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