1 use std::iter::{self, successors};
4 use ide_db::{ty_filter::TryEnum, RootDatabase};
8 edit::{AstNodeEdit, IndentLevel},
15 utils::{does_pat_match_variant, unwrap_trivial_block},
16 AssistContext, AssistId, AssistKind, Assists,
19 // Assist: replace_if_let_with_match
21 // Replaces a `if let` expression with a `match` expression.
24 // enum Action { Move { distance: u32 }, Stop }
26 // fn handle(action: Action) {
27 // $0if let Action::Move { distance } = action {
36 // enum Action { Move { distance: u32 }, Stop }
38 // fn handle(action: Action) {
40 // Action::Move { distance } => foo(distance),
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 mut else_block = None;
48 let if_exprs = successors(Some(if_expr.clone()), |expr| match expr.else_branch()? {
49 ast::ElseBranch::IfExpr(expr) => Some(expr),
50 ast::ElseBranch::Block(block) => {
51 else_block = Some(block);
55 let scrutinee_to_be_expr = if_expr.condition()?.expr()?;
57 let mut pat_seen = false;
58 let mut cond_bodies = Vec::new();
59 for if_expr in if_exprs {
60 let cond = if_expr.condition()?;
61 let expr = cond.expr()?;
62 let cond = match cond.pat() {
64 if scrutinee_to_be_expr.syntax().text() != expr.syntax().text() {
65 // Only if all condition expressions are equal we can merge them into a match
71 None => Either::Right(expr),
73 let body = if_expr.then_branch()?;
74 cond_bodies.push((cond, body));
78 // Don't offer turning an if (chain) without patterns into a match
82 let target = if_expr.syntax().text_range();
84 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
85 "Replace if let with match",
89 let else_arm = make_else_arm(ctx, else_block, &cond_bodies);
90 let make_match_arm = |(pat, body): (_, ast::BlockExpr)| {
91 let body = body.reset_indent().indent(IndentLevel(1));
93 Either::Left(pat) => {
94 make::match_arm(iter::once(pat), None, unwrap_trivial_block(body))
96 Either::Right(expr) => make::match_arm(
97 iter::once(make::wildcard_pat().into()),
99 unwrap_trivial_block(body),
103 let arms = cond_bodies.into_iter().map(make_match_arm).chain(iter::once(else_arm));
104 let match_expr = make::expr_match(scrutinee_to_be_expr, make::match_arm_list(arms));
105 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
108 let has_preceding_if_expr =
109 if_expr.syntax().parent().map_or(false, |it| ast::IfExpr::can_cast(it.kind()));
110 let expr = if has_preceding_if_expr {
111 // make sure we replace the `else if let ...` with a block so we don't end up with `else expr`
112 make::block_expr(None, Some(match_expr)).into()
116 edit.replace_ast::<ast::Expr>(if_expr.into(), expr);
123 else_block: Option<ast::BlockExpr>,
124 conditionals: &[(Either<ast::Pat, ast::Expr>, ast::BlockExpr)],
126 if let Some(else_block) = else_block {
127 let pattern = if let [(Either::Left(pat), _)] = conditionals {
130 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
135 let pattern = match pattern {
137 if does_pat_match_variant(&pat, &it.sad_pattern()) {
143 None => make::wildcard_pat().into(),
145 make::match_arm(iter::once(pattern), None, unwrap_trivial_block(else_block))
147 make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit().into())
151 // Assist: replace_match_with_if_let
153 // Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
156 // enum Action { Move { distance: u32 }, Stop }
158 // fn handle(action: Action) {
160 // Action::Move { distance } => foo(distance),
167 // enum Action { Move { distance: u32 }, Stop }
169 // fn handle(action: Action) {
170 // if let Action::Move { distance } = action {
177 pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
178 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
180 let mut arms = match_expr.match_arm_list()?.arms();
181 let (first_arm, second_arm) = (arms.next()?, arms.next()?);
182 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
186 let (if_let_pat, then_expr, else_expr) = pick_pattern_and_expr_order(
193 let scrutinee = match_expr.expr()?;
195 let target = match_expr.syntax().text_range();
197 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
198 "Replace match with if let",
201 let condition = make::condition(scrutinee, Some(if_let_pat));
202 let then_block = match then_expr.reset_indent() {
203 ast::Expr::BlockExpr(block) => block,
204 expr => make::block_expr(iter::empty(), Some(expr)),
206 let else_expr = match else_expr {
207 ast::Expr::BlockExpr(block) if block.is_empty() => None,
208 ast::Expr::TupleExpr(tuple) if tuple.fields().next().is_none() => None,
211 let if_let_expr = make::expr_if(
215 .map(|expr| match expr {
216 ast::Expr::BlockExpr(block) => block,
217 expr => (make::block_expr(iter::empty(), Some(expr))),
219 .map(ast::ElseBranch::Block),
221 .indent(IndentLevel::from_node(match_expr.syntax()));
223 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
228 /// Pick the pattern for the if let condition and return the expressions for the `then` body and `else` body in that order.
229 fn pick_pattern_and_expr_order(
230 sema: &hir::Semantics<RootDatabase>,
235 ) -> Option<(ast::Pat, ast::Expr, ast::Expr)> {
236 let res = match (pat, pat2) {
237 (ast::Pat::WildcardPat(_), _) => return None,
238 (pat, _) if expr2.syntax().first_child().is_none() => (pat, expr, expr2),
239 (_, pat) if expr.syntax().first_child().is_none() => (pat, expr2, expr),
240 (pat, pat2) => match (binds_name(&pat), binds_name(&pat2)) {
241 (true, false) => (pat, expr, expr2),
242 (false, true) => (pat2, expr2, expr),
243 _ if is_sad_pat(sema, &pat2) => (pat, expr, expr2),
244 _ if is_sad_pat(sema, &pat) => (pat2, expr2, expr),
245 (true, true) => return None,
246 (false, false) => (pat, expr, expr2),
252 fn binds_name(pat: &ast::Pat) -> bool {
253 let binds_name_v = |pat| binds_name(&pat);
255 ast::Pat::IdentPat(_) => true,
256 ast::Pat::MacroPat(_) => true,
257 ast::Pat::OrPat(pat) => pat.pats().any(binds_name_v),
258 ast::Pat::SlicePat(pat) => pat.pats().any(binds_name_v),
259 ast::Pat::TuplePat(it) => it.fields().any(binds_name_v),
260 ast::Pat::TupleStructPat(it) => it.fields().any(binds_name_v),
261 ast::Pat::RecordPat(it) => it
262 .record_pat_field_list()
263 .map_or(false, |rpfl| rpfl.fields().flat_map(|rpf| rpf.pat()).any(binds_name_v)),
264 ast::Pat::RefPat(pat) => pat.pat().map_or(false, binds_name_v),
265 ast::Pat::BoxPat(pat) => pat.pat().map_or(false, binds_name_v),
266 ast::Pat::ParenPat(pat) => pat.pat().map_or(false, binds_name_v),
271 fn is_sad_pat(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
272 sema.type_of_pat(pat)
273 .and_then(|ty| TryEnum::from_ty(sema, &ty.adjusted()))
274 .map_or(false, |it| does_pat_match_variant(pat, &it.sad_pattern()))
281 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
284 fn test_if_let_with_match_unapplicable_for_simple_ifs() {
285 check_assist_not_applicable(
286 replace_if_let_with_match,
289 if $0true {} else if false {} else {}
296 fn test_if_let_with_match_no_else() {
298 replace_if_let_with_match,
302 if $0let VariantData::Struct(..) = *self {
312 VariantData::Struct(..) => {
324 fn test_if_let_with_match_basic() {
326 replace_if_let_with_match,
329 pub fn is_struct(&self) -> bool {
330 if $0let VariantData::Struct(..) = *self {
332 } else if let VariantData::Tuple(..) = *self {
346 pub fn is_struct(&self) -> bool {
348 VariantData::Struct(..) => true,
349 VariantData::Tuple(..) => false,
364 fn test_if_let_with_match_on_tail_if_let() {
366 replace_if_let_with_match,
369 pub fn is_struct(&self) -> bool {
370 if let VariantData::Struct(..) = *self {
372 } else if let$0 VariantData::Tuple(..) = *self {
382 pub fn is_struct(&self) -> bool {
383 if let VariantData::Struct(..) = *self {
387 VariantData::Tuple(..) => false,
398 fn special_case_option() {
400 replace_if_let_with_match,
403 fn foo(x: Option<i32>) {
404 $0if let Some(x) = x {
412 fn foo(x: Option<i32>) {
414 Some(x) => println!("{}", x),
415 None => println!("none"),
423 fn special_case_inverted_option() {
425 replace_if_let_with_match,
428 fn foo(x: Option<i32>) {
437 fn foo(x: Option<i32>) {
439 None => println!("none"),
440 Some(_) => println!("some"),
448 fn special_case_result() {
450 replace_if_let_with_match,
453 fn foo(x: Result<i32, ()>) {
462 fn foo(x: Result<i32, ()>) {
464 Ok(x) => println!("{}", x),
465 Err(_) => println!("none"),
473 fn special_case_inverted_result() {
475 replace_if_let_with_match,
478 fn foo(x: Result<i32, ()>) {
479 $0if let Err(x) = x {
487 fn foo(x: Result<i32, ()>) {
489 Err(x) => println!("{}", x),
490 Ok(_) => println!("ok"),
500 replace_if_let_with_match,
504 $0if let Ok(rel_path) = path.strip_prefix(root_path) {
505 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
506 Some((*id, rel_path))
516 match path.strip_prefix(root_path) {
518 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
519 Some((*id, rel_path))
530 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
532 replace_match_with_if_let,
535 pub fn is_struct(&self) -> bool {
537 VariantData::Struct(..) => true,
544 pub fn is_struct(&self) -> bool {
545 if let VariantData::Struct(..) = *self {
556 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
558 replace_match_with_if_let,
562 VariantData::Struct(..) => {
572 if let VariantData::Struct(..) = a {
584 fn replace_match_with_if_let_target() {
586 replace_match_with_if_let,
589 pub fn is_struct(&self) -> bool {
591 VariantData::Struct(..) => true,
597 VariantData::Struct(..) => true,
604 fn special_case_option_match_to_if_let() {
606 replace_match_with_if_let,
609 fn foo(x: Option<i32>) {
611 Some(x) => println!("{}", x),
612 None => println!("none"),
617 fn foo(x: Option<i32>) {
629 fn special_case_result_match_to_if_let() {
631 replace_match_with_if_let,
634 fn foo(x: Result<i32, ()>) {
636 Ok(x) => println!("{}", x),
637 Err(_) => println!("none"),
642 fn foo(x: Result<i32, ()>) {
654 fn nested_indent_match_to_if_let() {
656 replace_match_with_if_let,
660 $0match path.strip_prefix(root_path) {
662 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
663 Some((*id, rel_path))
673 if let Ok(rel_path) = path.strip_prefix(root_path) {
674 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
675 Some((*id, rel_path))
686 fn replace_match_with_if_let_empty_wildcard_expr() {
688 replace_match_with_if_let,
691 $0match path.strip_prefix(root_path) {
692 Ok(rel_path) => println!("{}", rel_path),
699 if let Ok(rel_path) = path.strip_prefix(root_path) {
700 println!("{}", rel_path)
708 fn replace_match_with_if_let_exhaustive() {
710 replace_match_with_if_let,
712 fn print_source(def_source: ModuleSource) {
714 ModuleSource::SourceFile(..) => { println!("source file"); }
715 ModuleSource::Module(..) => { println!("module"); }
720 fn print_source(def_source: ModuleSource) {
721 if let ModuleSource::SourceFile(..) = def_source { println!("source file"); } else { println!("module"); }
728 fn replace_match_with_if_let_prefer_name_bind() {
730 replace_match_with_if_let,
735 Bar(bar) => println!("bar {}", bar),
741 if let Bar(bar) = Foo(0) {
742 println!("bar {}", bar)
748 replace_match_with_if_let,
752 Bar(bar) => println!("bar {}", bar),
759 if let Bar(bar) = Foo(0) {
760 println!("bar {}", bar)
768 fn replace_match_with_if_let_prefer_nonempty_body() {
770 replace_match_with_if_let,
775 Err(err) => eprintln!("{}", err),
781 if let Err(err) = Ok(0) {
788 replace_match_with_if_let,
792 Err(err) => eprintln!("{}", err),
799 if let Err(err) = Ok(0) {
808 fn replace_match_with_if_let_rejects_double_name_bindings() {
809 check_assist_not_applicable(
810 replace_match_with_if_let,
814 Foo(foo) => println!("bar {}", foo),
815 Bar(bar) => println!("bar {}", bar),