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