1 use std::iter::{self, successors};
6 syntax_helpers::node_ext::{is_pattern_cond, single_let},
13 edit::{AstNodeEdit, IndentLevel},
20 utils::{does_nested_pattern, does_pat_match_variant, unwrap_trivial_block},
21 AssistContext, AssistId, AssistKind, Assists,
24 // Assist: replace_if_let_with_match
26 // Replaces a `if let` expression with a `match` expression.
29 // enum Action { Move { distance: u32 }, Stop }
31 // fn handle(action: Action) {
32 // $0if let Action::Move { distance } = action {
41 // enum Action { Move { distance: u32 }, Stop }
43 // fn handle(action: Action) {
45 // Action::Move { distance } => foo(distance),
50 pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
51 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
52 let available_range = TextRange::new(
53 if_expr.syntax().text_range().start(),
54 if_expr.then_branch()?.syntax().text_range().start(),
56 let cursor_in_range = available_range.contains_range(ctx.selection_trimmed());
60 let mut else_block = None;
61 let if_exprs = successors(Some(if_expr.clone()), |expr| match expr.else_branch()? {
62 ast::ElseBranch::IfExpr(expr) => Some(expr),
63 ast::ElseBranch::Block(block) => {
64 else_block = Some(block);
68 let scrutinee_to_be_expr = if_expr.condition()?;
69 let scrutinee_to_be_expr = match single_let(scrutinee_to_be_expr.clone()) {
70 Some(cond) => cond.expr()?,
71 None => scrutinee_to_be_expr,
74 let mut pat_seen = false;
75 let mut cond_bodies = Vec::new();
76 for if_expr in if_exprs {
77 let cond = if_expr.condition()?;
78 let cond = match single_let(cond.clone()) {
80 let pat = let_.pat()?;
81 let expr = let_.expr()?;
82 // FIXME: If one `let` is wrapped in parentheses and the second is not,
84 if scrutinee_to_be_expr.syntax().text() != expr.syntax().text() {
85 // Only if all condition expressions are equal we can merge them into a match
91 // Multiple `let`, unsupported.
92 None if is_pattern_cond(cond.clone()) => return None,
93 None => Either::Right(cond),
95 let body = if_expr.then_branch()?;
96 cond_bodies.push((cond, body));
100 // Don't offer turning an if (chain) without patterns into a match
105 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
106 "Replace if let with match",
110 let else_arm = make_else_arm(ctx, else_block, &cond_bodies);
111 let make_match_arm = |(pat, body): (_, ast::BlockExpr)| {
112 let body = body.reset_indent().indent(IndentLevel(1));
114 Either::Left(pat) => {
115 make::match_arm(iter::once(pat), None, unwrap_trivial_block(body))
117 Either::Right(expr) => make::match_arm(
118 iter::once(make::wildcard_pat().into()),
120 unwrap_trivial_block(body),
124 let arms = cond_bodies.into_iter().map(make_match_arm).chain(iter::once(else_arm));
125 let match_expr = make::expr_match(scrutinee_to_be_expr, make::match_arm_list(arms));
126 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
129 let has_preceding_if_expr =
130 if_expr.syntax().parent().map_or(false, |it| ast::IfExpr::can_cast(it.kind()));
131 let expr = if has_preceding_if_expr {
132 // make sure we replace the `else if let ...` with a block so we don't end up with `else expr`
133 make::block_expr(None, Some(match_expr)).into()
137 edit.replace_ast::<ast::Expr>(if_expr.into(), expr);
143 ctx: &AssistContext<'_>,
144 else_block: Option<ast::BlockExpr>,
145 conditionals: &[(Either<ast::Pat, ast::Expr>, ast::BlockExpr)],
147 if let Some(else_block) = else_block {
148 let pattern = if let [(Either::Left(pat), _)] = conditionals {
151 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
156 let pattern = match pattern {
158 if does_pat_match_variant(pat, &it.sad_pattern()) {
159 it.happy_pattern_wildcard()
160 } else if does_nested_pattern(pat) {
161 make::wildcard_pat().into()
166 None => make::wildcard_pat().into(),
168 make::match_arm(iter::once(pattern), None, unwrap_trivial_block(else_block))
170 make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit())
174 // Assist: replace_match_with_if_let
176 // Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
179 // enum Action { Move { distance: u32 }, Stop }
181 // fn handle(action: Action) {
183 // Action::Move { distance } => foo(distance),
190 // enum Action { Move { distance: u32 }, Stop }
192 // fn handle(action: Action) {
193 // if let Action::Move { distance } = action {
200 pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
201 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
203 let mut arms = match_expr.match_arm_list()?.arms();
204 let (first_arm, second_arm) = (arms.next()?, arms.next()?);
205 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
209 let (if_let_pat, then_expr, else_expr) = pick_pattern_and_expr_order(
216 let scrutinee = match_expr.expr()?;
218 let target = match_expr.syntax().text_range();
220 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
221 "Replace match with if let",
224 fn make_block_expr(expr: ast::Expr) -> ast::BlockExpr {
225 // Blocks with modifiers (unsafe, async, etc.) are parsed as BlockExpr, but are
226 // formatted without enclosing braces. If we encounter such block exprs,
227 // wrap them in another BlockExpr.
229 ast::Expr::BlockExpr(block) if block.modifier().is_none() => block,
230 expr => make::block_expr(iter::empty(), Some(expr)),
234 let condition = make::expr_let(if_let_pat, scrutinee);
235 let then_block = make_block_expr(then_expr.reset_indent());
236 let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) };
237 let if_let_expr = make::expr_if(
240 else_expr.map(make_block_expr).map(ast::ElseBranch::Block),
242 .indent(IndentLevel::from_node(match_expr.syntax()));
244 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
249 /// Pick the pattern for the if let condition and return the expressions for the `then` body and `else` body in that order.
250 fn pick_pattern_and_expr_order(
251 sema: &hir::Semantics<'_, RootDatabase>,
256 ) -> Option<(ast::Pat, ast::Expr, ast::Expr)> {
257 let res = match (pat, pat2) {
258 (ast::Pat::WildcardPat(_), _) => return None,
259 (pat, ast::Pat::WildcardPat(_)) => (pat, expr, expr2),
260 (pat, _) if is_empty_expr(&expr2) => (pat, expr, expr2),
261 (_, pat) if is_empty_expr(&expr) => (pat, expr2, expr),
262 (pat, pat2) => match (binds_name(sema, &pat), binds_name(sema, &pat2)) {
263 (true, true) => return None,
264 (true, false) => (pat, expr, expr2),
265 (false, true) => (pat2, expr2, expr),
266 _ if is_sad_pat(sema, &pat) => (pat2, expr2, expr),
267 (false, false) => (pat, expr, expr2),
273 fn is_empty_expr(expr: &ast::Expr) -> bool {
275 ast::Expr::BlockExpr(expr) => match expr.stmt_list() {
276 Some(it) => it.statements().next().is_none() && it.tail_expr().is_none(),
279 ast::Expr::TupleExpr(expr) => expr.fields().next().is_none(),
284 fn binds_name(sema: &hir::Semantics<'_, RootDatabase>, pat: &ast::Pat) -> bool {
285 let binds_name_v = |pat| binds_name(sema, &pat);
287 ast::Pat::IdentPat(pat) => !matches!(
288 pat.name().and_then(|name| NameClass::classify(sema, &name)),
289 Some(NameClass::ConstReference(_))
291 ast::Pat::MacroPat(_) => true,
292 ast::Pat::OrPat(pat) => pat.pats().any(binds_name_v),
293 ast::Pat::SlicePat(pat) => pat.pats().any(binds_name_v),
294 ast::Pat::TuplePat(it) => it.fields().any(binds_name_v),
295 ast::Pat::TupleStructPat(it) => it.fields().any(binds_name_v),
296 ast::Pat::RecordPat(it) => it
297 .record_pat_field_list()
298 .map_or(false, |rpfl| rpfl.fields().flat_map(|rpf| rpf.pat()).any(binds_name_v)),
299 ast::Pat::RefPat(pat) => pat.pat().map_or(false, binds_name_v),
300 ast::Pat::BoxPat(pat) => pat.pat().map_or(false, binds_name_v),
301 ast::Pat::ParenPat(pat) => pat.pat().map_or(false, binds_name_v),
306 fn is_sad_pat(sema: &hir::Semantics<'_, RootDatabase>, pat: &ast::Pat) -> bool {
307 sema.type_of_pat(pat)
308 .and_then(|ty| TryEnum::from_ty(sema, &ty.adjusted()))
309 .map_or(false, |it| does_pat_match_variant(pat, &it.sad_pattern()))
316 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
319 fn test_if_let_with_match_unapplicable_for_simple_ifs() {
320 check_assist_not_applicable(
321 replace_if_let_with_match,
324 if $0true {} else if false {} else {}
331 fn test_if_let_with_match_no_else() {
333 replace_if_let_with_match,
337 if $0let VariantData::Struct(..) = *self {
347 VariantData::Struct(..) => {
359 fn test_if_let_with_match_available_range_left() {
360 check_assist_not_applicable(
361 replace_if_let_with_match,
365 $0 if let VariantData::Struct(..) = *self {
375 fn test_if_let_with_match_available_range_right() {
376 check_assist_not_applicable(
377 replace_if_let_with_match,
381 if let VariantData::Struct(..) = *self {$0
391 fn test_if_let_with_match_let_chain() {
392 check_assist_not_applicable(
393 replace_if_let_with_match,
396 if $0let true = true && let Some(1) = None {}
403 fn test_if_let_with_match_basic() {
405 replace_if_let_with_match,
408 pub fn is_struct(&self) -> bool {
409 if $0let VariantData::Struct(..) = *self {
411 } else if let VariantData::Tuple(..) = *self {
425 pub fn is_struct(&self) -> bool {
427 VariantData::Struct(..) => true,
428 VariantData::Tuple(..) => false,
443 fn test_if_let_with_match_on_tail_if_let() {
445 replace_if_let_with_match,
448 pub fn is_struct(&self) -> bool {
449 if let VariantData::Struct(..) = *self {
451 } else if let$0 VariantData::Tuple(..) = *self {
461 pub fn is_struct(&self) -> bool {
462 if let VariantData::Struct(..) = *self {
466 VariantData::Tuple(..) => false,
477 fn special_case_option() {
479 replace_if_let_with_match,
482 fn foo(x: Option<i32>) {
483 $0if let Some(x) = x {
491 fn foo(x: Option<i32>) {
493 Some(x) => println!("{}", x),
494 None => println!("none"),
502 fn special_case_inverted_option() {
504 replace_if_let_with_match,
507 fn foo(x: Option<i32>) {
516 fn foo(x: Option<i32>) {
518 None => println!("none"),
519 Some(_) => println!("some"),
527 fn special_case_result() {
529 replace_if_let_with_match,
532 fn foo(x: Result<i32, ()>) {
541 fn foo(x: Result<i32, ()>) {
543 Ok(x) => println!("{}", x),
544 Err(_) => println!("none"),
552 fn special_case_inverted_result() {
554 replace_if_let_with_match,
557 fn foo(x: Result<i32, ()>) {
558 $0if let Err(x) = x {
566 fn foo(x: Result<i32, ()>) {
568 Err(x) => println!("{}", x),
569 Ok(_) => println!("ok"),
579 replace_if_let_with_match,
583 $0if let Ok(rel_path) = path.strip_prefix(root_path) {
584 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
585 Some((*id, rel_path))
595 match path.strip_prefix(root_path) {
597 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
598 Some((*id, rel_path))
611 replace_if_let_with_match,
614 fn foo(x: Result<i32, ()>) {
615 let bar: Result<_, ()> = Ok(Some(1));
616 $0if let Ok(Some(_)) = bar {
624 fn foo(x: Result<i32, ()>) {
625 let bar: Result<_, ()> = Ok(Some(1));
636 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
638 replace_match_with_if_let,
641 pub fn is_struct(&self) -> bool {
643 VariantData::Struct(..) => true,
650 pub fn is_struct(&self) -> bool {
651 if let VariantData::Struct(..) = *self {
662 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
664 replace_match_with_if_let,
668 VariantData::Struct(..) => {
678 if let VariantData::Struct(..) = a {
690 fn replace_match_with_if_let_target() {
692 replace_match_with_if_let,
695 pub fn is_struct(&self) -> bool {
697 VariantData::Struct(..) => true,
703 VariantData::Struct(..) => true,
710 fn special_case_option_match_to_if_let() {
712 replace_match_with_if_let,
715 fn foo(x: Option<i32>) {
717 Some(x) => println!("{}", x),
718 None => println!("none"),
723 fn foo(x: Option<i32>) {
735 fn special_case_result_match_to_if_let() {
737 replace_match_with_if_let,
740 fn foo(x: Result<i32, ()>) {
742 Ok(x) => println!("{}", x),
743 Err(_) => println!("none"),
748 fn foo(x: Result<i32, ()>) {
760 fn nested_indent_match_to_if_let() {
762 replace_match_with_if_let,
766 $0match path.strip_prefix(root_path) {
768 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
769 Some((*id, rel_path))
779 if let Ok(rel_path) = path.strip_prefix(root_path) {
780 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
781 Some((*id, rel_path))
792 fn replace_match_with_if_let_empty_wildcard_expr() {
794 replace_match_with_if_let,
797 $0match path.strip_prefix(root_path) {
798 Ok(rel_path) => println!("{}", rel_path),
805 if let Ok(rel_path) = path.strip_prefix(root_path) {
806 println!("{}", rel_path)
814 fn replace_match_with_if_let_number_body() {
816 replace_match_with_if_let,
827 if let Err(_) = Ok(()) {
836 fn replace_match_with_if_let_exhaustive() {
838 replace_match_with_if_let,
840 fn print_source(def_source: ModuleSource) {
842 ModuleSource::SourceFile(..) => { println!("source file"); }
843 ModuleSource::Module(..) => { println!("module"); }
848 fn print_source(def_source: ModuleSource) {
849 if let ModuleSource::SourceFile(..) = def_source { println!("source file"); } else { println!("module"); }
856 fn replace_match_with_if_let_prefer_name_bind() {
858 replace_match_with_if_let,
863 Bar(bar) => println!("bar {}", bar),
869 if let Bar(bar) = Foo(0) {
870 println!("bar {}", bar)
876 replace_match_with_if_let,
880 Bar(bar) => println!("bar {}", bar),
887 if let Bar(bar) = Foo(0) {
888 println!("bar {}", bar)
896 fn replace_match_with_if_let_prefer_nonempty_body() {
898 replace_match_with_if_let,
903 Err(err) => eprintln!("{}", err),
909 if let Err(err) = Ok(0) {
916 replace_match_with_if_let,
920 Err(err) => eprintln!("{}", err),
927 if let Err(err) = Ok(0) {
936 fn replace_match_with_if_let_rejects_double_name_bindings() {
937 check_assist_not_applicable(
938 replace_match_with_if_let,
942 Foo(foo) => println!("bar {}", foo),
943 Bar(bar) => println!("bar {}", bar),
951 fn test_replace_match_with_if_let_keeps_unsafe_block() {
953 replace_match_with_if_let,
956 pub fn is_struct(&self) -> bool {
958 VariantData::Struct(..) => true,
959 _ => unsafe { unreachable_unchecked() },
965 pub fn is_struct(&self) -> bool {
966 if let VariantData::Struct(..) = *self {
969 unsafe { unreachable_unchecked() }
977 fn test_replace_match_with_if_let_forces_else() {
979 replace_match_with_if_let,