diff options
-rw-r--r-- | crates/assists/src/handlers/early_return.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/handlers/move_guard.rs | 1 | ||||
-rw-r--r-- | crates/assists/src/handlers/replace_if_let_with_match.rs | 277 | ||||
-rw-r--r-- | crates/assists/src/handlers/replace_let_with_if_let.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 28 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 13 |
7 files changed, 319 insertions, 5 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 @@ | |||
1 | use std::iter; | ||
2 | |||
3 | use ide_db::{ty_filter::TryEnum, RootDatabase}; | ||
1 | use syntax::{ | 4 | use syntax::{ |
2 | ast::{ | 5 | ast::{ |
3 | self, | 6 | self, |
@@ -8,7 +11,6 @@ use syntax::{ | |||
8 | }; | 11 | }; |
9 | 12 | ||
10 | use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; | 13 | use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; |
11 | use 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 | // ``` | ||
110 | pub(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 | |||
162 | fn 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)] |
83 | mod tests { | 170 | mod 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#" | ||
348 | impl VariantData { | ||
349 | pub fn is_struct(&self) -> bool { | ||
350 | <|>match *self { | ||
351 | VariantData::Struct(..) => true, | ||
352 | _ => false, | ||
353 | } | ||
354 | } | ||
355 | } "#, | ||
356 | r#" | ||
357 | impl 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#" | ||
374 | fn foo() { | ||
375 | <|>match a { | ||
376 | VariantData::Struct(..) => { | ||
377 | bar( | ||
378 | 123 | ||
379 | ) | ||
380 | } | ||
381 | _ => false, | ||
382 | } | ||
383 | } "#, | ||
384 | r#" | ||
385 | fn 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#" | ||
402 | impl 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#" | ||
422 | enum Option<T> { Some(T), None } | ||
423 | use Option::*; | ||
424 | |||
425 | fn foo(x: Option<i32>) { | ||
426 | <|>match x { | ||
427 | Some(x) => println!("{}", x), | ||
428 | None => println!("none"), | ||
429 | } | ||
430 | } | ||
431 | "#, | ||
432 | r#" | ||
433 | enum Option<T> { Some(T), None } | ||
434 | use Option::*; | ||
435 | |||
436 | fn 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#" | ||
452 | enum Result<T, E> { Ok(T), Err(E) } | ||
453 | use Result::*; | ||
454 | |||
455 | fn foo(x: Result<i32, ()>) { | ||
456 | <|>match x { | ||
457 | Ok(x) => println!("{}", x), | ||
458 | Err(_) => println!("none"), | ||
459 | } | ||
460 | } | ||
461 | "#, | ||
462 | r#" | ||
463 | enum Result<T, E> { Ok(T), Err(E) } | ||
464 | use Result::*; | ||
465 | |||
466 | fn 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#" | ||
482 | fn 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#" | ||
495 | fn 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#" | ||
514 | fn main() { | ||
515 | <|>match path.strip_prefix(root_path) { | ||
516 | Ok(rel_path) => println!("{}", rel_path), | ||
517 | _ => (), | ||
518 | } | ||
519 | } | ||
520 | "#, | ||
521 | r#" | ||
522 | fn 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] |
893 | fn doctest_replace_match_with_if_let() { | ||
894 | check_doc_test( | ||
895 | "replace_match_with_if_let", | ||
896 | r#####" | ||
897 | enum Action { Move { distance: u32 }, Stop } | ||
898 | |||
899 | fn handle(action: Action) { | ||
900 | <|>match action { | ||
901 | Action::Move { distance } => foo(distance), | ||
902 | _ => bar(), | ||
903 | } | ||
904 | } | ||
905 | "#####, | ||
906 | r#####" | ||
907 | enum Action { Move { distance: u32 }, Stop } | ||
908 | |||
909 | fn 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] | ||
893 | fn doctest_replace_qualified_name_with_use() { | 921 | fn 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", |
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 876659a2b..cc09b77a5 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs | |||
@@ -171,8 +171,17 @@ pub fn expr_return() -> ast::Expr { | |||
171 | pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { | 171 | pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { |
172 | expr_from_text(&format!("match {} {}", expr, match_arm_list)) | 172 | expr_from_text(&format!("match {} {}", expr, match_arm_list)) |
173 | } | 173 | } |
174 | pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr { | 174 | pub fn expr_if( |
175 | expr_from_text(&format!("if {} {}", condition, then_branch)) | 175 | condition: ast::Condition, |
176 | then_branch: ast::BlockExpr, | ||
177 | else_branch: Option<ast::ElseBranch>, | ||
178 | ) -> ast::Expr { | ||
179 | let else_branch = match else_branch { | ||
180 | Some(ast::ElseBranch::Block(block)) => format!("else {}", block), | ||
181 | Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {}", if_expr), | ||
182 | None => String::new(), | ||
183 | }; | ||
184 | expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch)) | ||
176 | } | 185 | } |
177 | pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { | 186 | pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { |
178 | let token = token(op); | 187 | let token = token(op); |