]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/replace_if_let_with_match.rs
Fix various IDE features
[rust.git] / crates / ide_assists / src / handlers / replace_if_let_with_match.rs
1 use std::iter::{self, successors};
2
3 use either::Either;
4 use ide_db::{defs::NameClass, ty_filter::TryEnum, RootDatabase};
5 use syntax::{
6     ast::{
7         self,
8         edit::{AstNodeEdit, IndentLevel},
9         make, HasName,
10     },
11     AstNode, TextRange,
12 };
13
14 use crate::{
15     utils::{does_nested_pattern, does_pat_match_variant, unwrap_trivial_block},
16     AssistContext, AssistId, AssistKind, Assists,
17 };
18
19 // Assist: replace_if_let_with_match
20 //
21 // Replaces a `if let` expression with a `match` expression.
22 //
23 // ```
24 // enum Action { Move { distance: u32 }, Stop }
25 //
26 // fn handle(action: Action) {
27 //     $0if let Action::Move { distance } = action {
28 //         foo(distance)
29 //     } else {
30 //         bar()
31 //     }
32 // }
33 // ```
34 // ->
35 // ```
36 // enum Action { Move { distance: u32 }, Stop }
37 //
38 // fn handle(action: Action) {
39 //     match action {
40 //         Action::Move { distance } => foo(distance),
41 //         _ => bar(),
42 //     }
43 // }
44 // ```
45 pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
46     let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
47     let available_range = TextRange::new(
48         if_expr.syntax().text_range().start(),
49         if_expr.then_branch()?.syntax().text_range().start(),
50     );
51     let cursor_in_range = available_range.contains_range(ctx.selection_trimmed());
52     if !cursor_in_range {
53         return None;
54     }
55     let mut else_block = None;
56     let if_exprs = successors(Some(if_expr.clone()), |expr| match expr.else_branch()? {
57         ast::ElseBranch::IfExpr(expr) => Some(expr),
58         ast::ElseBranch::Block(block) => {
59             else_block = Some(block);
60             None
61         }
62     });
63     let scrutinee_to_be_expr = if_expr.condition()?;
64     let scrutinee_to_be_expr = match scrutinee_to_be_expr.single_let() {
65         Some(cond) => cond.expr()?,
66         None => scrutinee_to_be_expr,
67     };
68
69     let mut pat_seen = false;
70     let mut cond_bodies = Vec::new();
71     for if_expr in if_exprs {
72         let cond = if_expr.condition()?;
73         let cond = match cond.single_let() {
74             Some(let_) => {
75                 let pat = let_.pat()?;
76                 let expr = let_.expr()?;
77                 // FIXME: If one `let` is wrapped in parentheses and the second is not,
78                 // we'll exit here.
79                 if scrutinee_to_be_expr.syntax().text() != expr.syntax().text() {
80                     // Only if all condition expressions are equal we can merge them into a match
81                     return None;
82                 }
83                 pat_seen = true;
84                 Either::Left(pat)
85             }
86             // Multiple `let`, unsupported.
87             None if cond.is_pattern_cond() => return None,
88             None => Either::Right(cond),
89         };
90         let body = if_expr.then_branch()?;
91         cond_bodies.push((cond, body));
92     }
93
94     if !pat_seen {
95         // Don't offer turning an if (chain) without patterns into a match
96         return None;
97     }
98
99     acc.add(
100         AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
101         "Replace if let with match",
102         available_range,
103         move |edit| {
104             let match_expr = {
105                 let else_arm = make_else_arm(ctx, else_block, &cond_bodies);
106                 let make_match_arm = |(pat, body): (_, ast::BlockExpr)| {
107                     let body = body.reset_indent().indent(IndentLevel(1));
108                     match pat {
109                         Either::Left(pat) => {
110                             make::match_arm(iter::once(pat), None, unwrap_trivial_block(body))
111                         }
112                         Either::Right(expr) => make::match_arm(
113                             iter::once(make::wildcard_pat().into()),
114                             Some(expr),
115                             unwrap_trivial_block(body),
116                         ),
117                     }
118                 };
119                 let arms = cond_bodies.into_iter().map(make_match_arm).chain(iter::once(else_arm));
120                 let match_expr = make::expr_match(scrutinee_to_be_expr, make::match_arm_list(arms));
121                 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
122             };
123
124             let has_preceding_if_expr =
125                 if_expr.syntax().parent().map_or(false, |it| ast::IfExpr::can_cast(it.kind()));
126             let expr = if has_preceding_if_expr {
127                 // make sure we replace the `else if let ...` with a block so we don't end up with `else expr`
128                 make::block_expr(None, Some(match_expr)).into()
129             } else {
130                 match_expr
131             };
132             edit.replace_ast::<ast::Expr>(if_expr.into(), expr);
133         },
134     )
135 }
136
137 fn make_else_arm(
138     ctx: &AssistContext,
139     else_block: Option<ast::BlockExpr>,
140     conditionals: &[(Either<ast::Pat, ast::Expr>, ast::BlockExpr)],
141 ) -> ast::MatchArm {
142     if let Some(else_block) = else_block {
143         let pattern = if let [(Either::Left(pat), _)] = conditionals {
144             ctx.sema
145                 .type_of_pat(pat)
146                 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
147                 .zip(Some(pat))
148         } else {
149             None
150         };
151         let pattern = match pattern {
152             Some((it, pat)) => {
153                 if does_pat_match_variant(pat, &it.sad_pattern()) {
154                     it.happy_pattern_wildcard()
155                 } else if does_nested_pattern(pat) {
156                     make::wildcard_pat().into()
157                 } else {
158                     it.sad_pattern()
159                 }
160             }
161             None => make::wildcard_pat().into(),
162         };
163         make::match_arm(iter::once(pattern), None, unwrap_trivial_block(else_block))
164     } else {
165         make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit())
166     }
167 }
168
169 // Assist: replace_match_with_if_let
170 //
171 // Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
172 //
173 // ```
174 // enum Action { Move { distance: u32 }, Stop }
175 //
176 // fn handle(action: Action) {
177 //     $0match action {
178 //         Action::Move { distance } => foo(distance),
179 //         _ => bar(),
180 //     }
181 // }
182 // ```
183 // ->
184 // ```
185 // enum Action { Move { distance: u32 }, Stop }
186 //
187 // fn handle(action: Action) {
188 //     if let Action::Move { distance } = action {
189 //         foo(distance)
190 //     } else {
191 //         bar()
192 //     }
193 // }
194 // ```
195 pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
196     let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
197
198     let mut arms = match_expr.match_arm_list()?.arms();
199     let (first_arm, second_arm) = (arms.next()?, arms.next()?);
200     if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
201         return None;
202     }
203
204     let (if_let_pat, then_expr, else_expr) = pick_pattern_and_expr_order(
205         &ctx.sema,
206         first_arm.pat()?,
207         second_arm.pat()?,
208         first_arm.expr()?,
209         second_arm.expr()?,
210     )?;
211     let scrutinee = match_expr.expr()?;
212
213     let target = match_expr.syntax().text_range();
214     acc.add(
215         AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
216         "Replace match with if let",
217         target,
218         move |edit| {
219             fn make_block_expr(expr: ast::Expr) -> ast::BlockExpr {
220                 // Blocks with modifiers (unsafe, async, etc.) are parsed as BlockExpr, but are
221                 // formatted without enclosing braces. If we encounter such block exprs,
222                 // wrap them in another BlockExpr.
223                 match expr {
224                     ast::Expr::BlockExpr(block) if block.modifier().is_none() => block,
225                     expr => make::block_expr(iter::empty(), Some(expr)),
226                 }
227             }
228
229             let condition = make::expr_let(if_let_pat, scrutinee);
230             let then_block = make_block_expr(then_expr.reset_indent());
231             let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) };
232             let if_let_expr = make::expr_if(
233                 condition.into(),
234                 then_block,
235                 else_expr.map(make_block_expr).map(ast::ElseBranch::Block),
236             )
237             .indent(IndentLevel::from_node(match_expr.syntax()));
238
239             edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
240         },
241     )
242 }
243
244 /// Pick the pattern for the if let condition and return the expressions for the `then` body and `else` body in that order.
245 fn pick_pattern_and_expr_order(
246     sema: &hir::Semantics<RootDatabase>,
247     pat: ast::Pat,
248     pat2: ast::Pat,
249     expr: ast::Expr,
250     expr2: ast::Expr,
251 ) -> Option<(ast::Pat, ast::Expr, ast::Expr)> {
252     let res = match (pat, pat2) {
253         (ast::Pat::WildcardPat(_), _) => return None,
254         (pat, _) if is_empty_expr(&expr2) => (pat, expr, expr2),
255         (_, pat) if is_empty_expr(&expr) => (pat, expr2, expr),
256         (pat, pat2) => match (binds_name(sema, &pat), binds_name(sema, &pat2)) {
257             (true, true) => return None,
258             (true, false) => (pat, expr, expr2),
259             (false, true) => (pat2, expr2, expr),
260             _ if is_sad_pat(sema, &pat) => (pat2, expr2, expr),
261             (false, false) => (pat, expr, expr2),
262         },
263     };
264     Some(res)
265 }
266
267 fn is_empty_expr(expr: &ast::Expr) -> bool {
268     match expr {
269         ast::Expr::BlockExpr(expr) => match expr.stmt_list() {
270             Some(it) => it.statements().next().is_none() && it.tail_expr().is_none(),
271             None => true,
272         },
273         ast::Expr::TupleExpr(expr) => expr.fields().next().is_none(),
274         _ => false,
275     }
276 }
277
278 fn binds_name(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
279     let binds_name_v = |pat| binds_name(sema, &pat);
280     match pat {
281         ast::Pat::IdentPat(pat) => !matches!(
282             pat.name().and_then(|name| NameClass::classify(sema, &name)),
283             Some(NameClass::ConstReference(_))
284         ),
285         ast::Pat::MacroPat(_) => true,
286         ast::Pat::OrPat(pat) => pat.pats().any(binds_name_v),
287         ast::Pat::SlicePat(pat) => pat.pats().any(binds_name_v),
288         ast::Pat::TuplePat(it) => it.fields().any(binds_name_v),
289         ast::Pat::TupleStructPat(it) => it.fields().any(binds_name_v),
290         ast::Pat::RecordPat(it) => it
291             .record_pat_field_list()
292             .map_or(false, |rpfl| rpfl.fields().flat_map(|rpf| rpf.pat()).any(binds_name_v)),
293         ast::Pat::RefPat(pat) => pat.pat().map_or(false, binds_name_v),
294         ast::Pat::BoxPat(pat) => pat.pat().map_or(false, binds_name_v),
295         ast::Pat::ParenPat(pat) => pat.pat().map_or(false, binds_name_v),
296         _ => false,
297     }
298 }
299
300 fn is_sad_pat(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
301     sema.type_of_pat(pat)
302         .and_then(|ty| TryEnum::from_ty(sema, &ty.adjusted()))
303         .map_or(false, |it| does_pat_match_variant(pat, &it.sad_pattern()))
304 }
305
306 #[cfg(test)]
307 mod tests {
308     use super::*;
309
310     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
311
312     #[test]
313     fn test_if_let_with_match_unapplicable_for_simple_ifs() {
314         check_assist_not_applicable(
315             replace_if_let_with_match,
316             r#"
317 fn main() {
318     if $0true {} else if false {} else {}
319 }
320 "#,
321         )
322     }
323
324     #[test]
325     fn test_if_let_with_match_no_else() {
326         check_assist(
327             replace_if_let_with_match,
328             r#"
329 impl VariantData {
330     pub fn foo(&self) {
331         if $0let VariantData::Struct(..) = *self {
332             self.foo();
333         }
334     }
335 }
336 "#,
337             r#"
338 impl VariantData {
339     pub fn foo(&self) {
340         match *self {
341             VariantData::Struct(..) => {
342                 self.foo();
343             }
344             _ => (),
345         }
346     }
347 }
348 "#,
349         )
350     }
351
352     #[test]
353     fn test_if_let_with_match_available_range_left() {
354         check_assist_not_applicable(
355             replace_if_let_with_match,
356             r#"
357 impl VariantData {
358     pub fn foo(&self) {
359         $0 if let VariantData::Struct(..) = *self {
360             self.foo();
361         }
362     }
363 }
364 "#,
365         )
366     }
367
368     #[test]
369     fn test_if_let_with_match_available_range_right() {
370         check_assist_not_applicable(
371             replace_if_let_with_match,
372             r#"
373 impl VariantData {
374     pub fn foo(&self) {
375         if let VariantData::Struct(..) = *self {$0
376             self.foo();
377         }
378     }
379 }
380 "#,
381         )
382     }
383
384     #[test]
385     fn test_if_let_with_match_basic() {
386         check_assist(
387             replace_if_let_with_match,
388             r#"
389 impl VariantData {
390     pub fn is_struct(&self) -> bool {
391         if $0let VariantData::Struct(..) = *self {
392             true
393         } else if let VariantData::Tuple(..) = *self {
394             false
395         } else if cond() {
396             true
397         } else {
398             bar(
399                 123
400             )
401         }
402     }
403 }
404 "#,
405             r#"
406 impl VariantData {
407     pub fn is_struct(&self) -> bool {
408         match *self {
409             VariantData::Struct(..) => true,
410             VariantData::Tuple(..) => false,
411             _ if cond() => true,
412             _ => {
413                     bar(
414                         123
415                     )
416                 }
417         }
418     }
419 }
420 "#,
421         )
422     }
423
424     #[test]
425     fn test_if_let_with_match_on_tail_if_let() {
426         check_assist(
427             replace_if_let_with_match,
428             r#"
429 impl VariantData {
430     pub fn is_struct(&self) -> bool {
431         if let VariantData::Struct(..) = *self {
432             true
433         } else if let$0 VariantData::Tuple(..) = *self {
434             false
435         } else {
436             false
437         }
438     }
439 }
440 "#,
441             r#"
442 impl VariantData {
443     pub fn is_struct(&self) -> bool {
444         if let VariantData::Struct(..) = *self {
445             true
446         } else {
447     match *self {
448             VariantData::Tuple(..) => false,
449             _ => false,
450         }
451 }
452     }
453 }
454 "#,
455         )
456     }
457
458     #[test]
459     fn special_case_option() {
460         check_assist(
461             replace_if_let_with_match,
462             r#"
463 //- minicore: option
464 fn foo(x: Option<i32>) {
465     $0if let Some(x) = x {
466         println!("{}", x)
467     } else {
468         println!("none")
469     }
470 }
471 "#,
472             r#"
473 fn foo(x: Option<i32>) {
474     match x {
475         Some(x) => println!("{}", x),
476         None => println!("none"),
477     }
478 }
479 "#,
480         );
481     }
482
483     #[test]
484     fn special_case_inverted_option() {
485         check_assist(
486             replace_if_let_with_match,
487             r#"
488 //- minicore: option
489 fn foo(x: Option<i32>) {
490     $0if let None = x {
491         println!("none")
492     } else {
493         println!("some")
494     }
495 }
496 "#,
497             r#"
498 fn foo(x: Option<i32>) {
499     match x {
500         None => println!("none"),
501         Some(_) => println!("some"),
502     }
503 }
504 "#,
505         );
506     }
507
508     #[test]
509     fn special_case_result() {
510         check_assist(
511             replace_if_let_with_match,
512             r#"
513 //- minicore: result
514 fn foo(x: Result<i32, ()>) {
515     $0if let Ok(x) = x {
516         println!("{}", x)
517     } else {
518         println!("none")
519     }
520 }
521 "#,
522             r#"
523 fn foo(x: Result<i32, ()>) {
524     match x {
525         Ok(x) => println!("{}", x),
526         Err(_) => println!("none"),
527     }
528 }
529 "#,
530         );
531     }
532
533     #[test]
534     fn special_case_inverted_result() {
535         check_assist(
536             replace_if_let_with_match,
537             r#"
538 //- minicore: result
539 fn foo(x: Result<i32, ()>) {
540     $0if let Err(x) = x {
541         println!("{}", x)
542     } else {
543         println!("ok")
544     }
545 }
546 "#,
547             r#"
548 fn foo(x: Result<i32, ()>) {
549     match x {
550         Err(x) => println!("{}", x),
551         Ok(_) => println!("ok"),
552     }
553 }
554 "#,
555         );
556     }
557
558     #[test]
559     fn nested_indent() {
560         check_assist(
561             replace_if_let_with_match,
562             r#"
563 fn main() {
564     if true {
565         $0if let Ok(rel_path) = path.strip_prefix(root_path) {
566             let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
567             Some((*id, rel_path))
568         } else {
569             None
570         }
571     }
572 }
573 "#,
574             r#"
575 fn main() {
576     if true {
577         match path.strip_prefix(root_path) {
578             Ok(rel_path) => {
579                 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
580                 Some((*id, rel_path))
581             }
582             _ => None,
583         }
584     }
585 }
586 "#,
587         )
588     }
589
590     #[test]
591     fn nested_type() {
592         check_assist(
593             replace_if_let_with_match,
594             r#"
595 //- minicore: result
596 fn foo(x: Result<i32, ()>) {
597     let bar: Result<_, ()> = Ok(Some(1));
598     $0if let Ok(Some(_)) = bar {
599         ()
600     } else {
601         ()
602     }
603 }
604 "#,
605             r#"
606 fn foo(x: Result<i32, ()>) {
607     let bar: Result<_, ()> = Ok(Some(1));
608     match bar {
609         Ok(Some(_)) => (),
610         _ => (),
611     }
612 }
613 "#,
614         );
615     }
616
617     #[test]
618     fn test_replace_match_with_if_let_unwraps_simple_expressions() {
619         check_assist(
620             replace_match_with_if_let,
621             r#"
622 impl VariantData {
623     pub fn is_struct(&self) -> bool {
624         $0match *self {
625             VariantData::Struct(..) => true,
626             _ => false,
627         }
628     }
629 }           "#,
630             r#"
631 impl VariantData {
632     pub fn is_struct(&self) -> bool {
633         if let VariantData::Struct(..) = *self {
634             true
635         } else {
636             false
637         }
638     }
639 }           "#,
640         )
641     }
642
643     #[test]
644     fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
645         check_assist(
646             replace_match_with_if_let,
647             r#"
648 fn foo() {
649     $0match a {
650         VariantData::Struct(..) => {
651             bar(
652                 123
653             )
654         }
655         _ => false,
656     }
657 }           "#,
658             r#"
659 fn foo() {
660     if let VariantData::Struct(..) = a {
661         bar(
662             123
663         )
664     } else {
665         false
666     }
667 }           "#,
668         )
669     }
670
671     #[test]
672     fn replace_match_with_if_let_target() {
673         check_assist_target(
674             replace_match_with_if_let,
675             r#"
676 impl VariantData {
677     pub fn is_struct(&self) -> bool {
678         $0match *self {
679             VariantData::Struct(..) => true,
680             _ => false,
681         }
682     }
683 }           "#,
684             r#"match *self {
685             VariantData::Struct(..) => true,
686             _ => false,
687         }"#,
688         );
689     }
690
691     #[test]
692     fn special_case_option_match_to_if_let() {
693         check_assist(
694             replace_match_with_if_let,
695             r#"
696 //- minicore: option
697 fn foo(x: Option<i32>) {
698     $0match x {
699         Some(x) => println!("{}", x),
700         None => println!("none"),
701     }
702 }
703 "#,
704             r#"
705 fn foo(x: Option<i32>) {
706     if let Some(x) = x {
707         println!("{}", x)
708     } else {
709         println!("none")
710     }
711 }
712 "#,
713         );
714     }
715
716     #[test]
717     fn special_case_result_match_to_if_let() {
718         check_assist(
719             replace_match_with_if_let,
720             r#"
721 //- minicore: result
722 fn foo(x: Result<i32, ()>) {
723     $0match x {
724         Ok(x) => println!("{}", x),
725         Err(_) => println!("none"),
726     }
727 }
728 "#,
729             r#"
730 fn foo(x: Result<i32, ()>) {
731     if let Ok(x) = x {
732         println!("{}", x)
733     } else {
734         println!("none")
735     }
736 }
737 "#,
738         );
739     }
740
741     #[test]
742     fn nested_indent_match_to_if_let() {
743         check_assist(
744             replace_match_with_if_let,
745             r#"
746 fn main() {
747     if true {
748         $0match path.strip_prefix(root_path) {
749             Ok(rel_path) => {
750                 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
751                 Some((*id, rel_path))
752             }
753             _ => None,
754         }
755     }
756 }
757 "#,
758             r#"
759 fn main() {
760     if true {
761         if let Ok(rel_path) = path.strip_prefix(root_path) {
762             let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
763             Some((*id, rel_path))
764         } else {
765             None
766         }
767     }
768 }
769 "#,
770         )
771     }
772
773     #[test]
774     fn replace_match_with_if_let_empty_wildcard_expr() {
775         check_assist(
776             replace_match_with_if_let,
777             r#"
778 fn main() {
779     $0match path.strip_prefix(root_path) {
780         Ok(rel_path) => println!("{}", rel_path),
781         _ => (),
782     }
783 }
784 "#,
785             r#"
786 fn main() {
787     if let Ok(rel_path) = path.strip_prefix(root_path) {
788         println!("{}", rel_path)
789     }
790 }
791 "#,
792         )
793     }
794
795     #[test]
796     fn replace_match_with_if_let_number_body() {
797         check_assist(
798             replace_match_with_if_let,
799             r#"
800 fn main() {
801     $0match Ok(()) {
802         Ok(()) => {},
803         Err(_) => 0,
804     }
805 }
806 "#,
807             r#"
808 fn main() {
809     if let Err(_) = Ok(()) {
810         0
811     }
812 }
813 "#,
814         )
815     }
816
817     #[test]
818     fn replace_match_with_if_let_exhaustive() {
819         check_assist(
820             replace_match_with_if_let,
821             r#"
822 fn print_source(def_source: ModuleSource) {
823     match def_so$0urce {
824         ModuleSource::SourceFile(..) => { println!("source file"); }
825         ModuleSource::Module(..) => { println!("module"); }
826     }
827 }
828 "#,
829             r#"
830 fn print_source(def_source: ModuleSource) {
831     if let ModuleSource::SourceFile(..) = def_source { println!("source file"); } else { println!("module"); }
832 }
833 "#,
834         )
835     }
836
837     #[test]
838     fn replace_match_with_if_let_prefer_name_bind() {
839         check_assist(
840             replace_match_with_if_let,
841             r#"
842 fn foo() {
843     match $0Foo(0) {
844         Foo(_) => (),
845         Bar(bar) => println!("bar {}", bar),
846     }
847 }
848 "#,
849             r#"
850 fn foo() {
851     if let Bar(bar) = Foo(0) {
852         println!("bar {}", bar)
853     }
854 }
855 "#,
856         );
857         check_assist(
858             replace_match_with_if_let,
859             r#"
860 fn foo() {
861     match $0Foo(0) {
862         Bar(bar) => println!("bar {}", bar),
863         Foo(_) => (),
864     }
865 }
866 "#,
867             r#"
868 fn foo() {
869     if let Bar(bar) = Foo(0) {
870         println!("bar {}", bar)
871     }
872 }
873 "#,
874         );
875     }
876
877     #[test]
878     fn replace_match_with_if_let_prefer_nonempty_body() {
879         check_assist(
880             replace_match_with_if_let,
881             r#"
882 fn foo() {
883     match $0Ok(0) {
884         Ok(value) => {},
885         Err(err) => eprintln!("{}", err),
886     }
887 }
888 "#,
889             r#"
890 fn foo() {
891     if let Err(err) = Ok(0) {
892         eprintln!("{}", err)
893     }
894 }
895 "#,
896         );
897         check_assist(
898             replace_match_with_if_let,
899             r#"
900 fn foo() {
901     match $0Ok(0) {
902         Err(err) => eprintln!("{}", err),
903         Ok(value) => {},
904     }
905 }
906 "#,
907             r#"
908 fn foo() {
909     if let Err(err) = Ok(0) {
910         eprintln!("{}", err)
911     }
912 }
913 "#,
914         );
915     }
916
917     #[test]
918     fn replace_match_with_if_let_rejects_double_name_bindings() {
919         check_assist_not_applicable(
920             replace_match_with_if_let,
921             r#"
922 fn foo() {
923     match $0Foo(0) {
924         Foo(foo) => println!("bar {}", foo),
925         Bar(bar) => println!("bar {}", bar),
926     }
927 }
928 "#,
929         );
930     }
931
932     #[test]
933     fn test_replace_match_with_if_let_keeps_unsafe_block() {
934         check_assist(
935             replace_match_with_if_let,
936             r#"
937 impl VariantData {
938     pub fn is_struct(&self) -> bool {
939         $0match *self {
940             VariantData::Struct(..) => true,
941             _ => unsafe { unreachable_unchecked() },
942         }
943     }
944 }           "#,
945             r#"
946 impl VariantData {
947     pub fn is_struct(&self) -> bool {
948         if let VariantData::Struct(..) = *self {
949             true
950         } else {
951             unsafe { unreachable_unchecked() }
952         }
953     }
954 }           "#,
955         )
956     }
957 }