1 use std::iter::{self, successors};
4 use ide_db::{defs::NameClass, ty_filter::TryEnum, RootDatabase};
8 edit::{AstNodeEdit, IndentLevel},
15 utils::{does_nested_pattern, 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 available_range = TextRange::new(
48 if_expr.syntax().text_range().start(),
49 if_expr.then_branch()?.syntax().text_range().start(),
51 let cursor_in_range = available_range.contains_range(ctx.selection_trimmed());
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);
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,
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() {
75 let pat = let_.pat()?;
76 let expr = let_.expr()?;
77 // FIXME: If one `let` is wrapped in parentheses and the second is not,
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
86 // Multiple `let`, unsupported.
87 None if cond.is_pattern_cond() => return None,
88 None => Either::Right(cond),
90 let body = if_expr.then_branch()?;
91 cond_bodies.push((cond, body));
95 // Don't offer turning an if (chain) without patterns into a match
100 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
101 "Replace if let with match",
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));
109 Either::Left(pat) => {
110 make::match_arm(iter::once(pat), None, unwrap_trivial_block(body))
112 Either::Right(expr) => make::match_arm(
113 iter::once(make::wildcard_pat().into()),
115 unwrap_trivial_block(body),
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()))
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()
132 edit.replace_ast::<ast::Expr>(if_expr.into(), expr);
139 else_block: Option<ast::BlockExpr>,
140 conditionals: &[(Either<ast::Pat, ast::Expr>, ast::BlockExpr)],
142 if let Some(else_block) = else_block {
143 let pattern = if let [(Either::Left(pat), _)] = conditionals {
146 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
151 let pattern = match pattern {
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()
161 None => make::wildcard_pat().into(),
163 make::match_arm(iter::once(pattern), None, unwrap_trivial_block(else_block))
165 make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit())
169 // Assist: replace_match_with_if_let
171 // Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
174 // enum Action { Move { distance: u32 }, Stop }
176 // fn handle(action: Action) {
178 // Action::Move { distance } => foo(distance),
185 // enum Action { Move { distance: u32 }, Stop }
187 // fn handle(action: Action) {
188 // if let Action::Move { distance } = action {
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()?;
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() {
204 let (if_let_pat, then_expr, else_expr) = pick_pattern_and_expr_order(
211 let scrutinee = match_expr.expr()?;
213 let target = match_expr.syntax().text_range();
215 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
216 "Replace match with if let",
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.
224 ast::Expr::BlockExpr(block) if block.modifier().is_none() => block,
225 expr => make::block_expr(iter::empty(), Some(expr)),
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(
235 else_expr.map(make_block_expr).map(ast::ElseBranch::Block),
237 .indent(IndentLevel::from_node(match_expr.syntax()));
239 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
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>,
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),
267 fn is_empty_expr(expr: &ast::Expr) -> bool {
269 ast::Expr::BlockExpr(expr) => match expr.stmt_list() {
270 Some(it) => it.statements().next().is_none() && it.tail_expr().is_none(),
273 ast::Expr::TupleExpr(expr) => expr.fields().next().is_none(),
278 fn binds_name(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
279 let binds_name_v = |pat| binds_name(sema, &pat);
281 ast::Pat::IdentPat(pat) => !matches!(
282 pat.name().and_then(|name| NameClass::classify(sema, &name)),
283 Some(NameClass::ConstReference(_))
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),
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()))
310 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
313 fn test_if_let_with_match_unapplicable_for_simple_ifs() {
314 check_assist_not_applicable(
315 replace_if_let_with_match,
318 if $0true {} else if false {} else {}
325 fn test_if_let_with_match_no_else() {
327 replace_if_let_with_match,
331 if $0let VariantData::Struct(..) = *self {
341 VariantData::Struct(..) => {
353 fn test_if_let_with_match_available_range_left() {
354 check_assist_not_applicable(
355 replace_if_let_with_match,
359 $0 if let VariantData::Struct(..) = *self {
369 fn test_if_let_with_match_available_range_right() {
370 check_assist_not_applicable(
371 replace_if_let_with_match,
375 if let VariantData::Struct(..) = *self {$0
385 fn test_if_let_with_match_basic() {
387 replace_if_let_with_match,
390 pub fn is_struct(&self) -> bool {
391 if $0let VariantData::Struct(..) = *self {
393 } else if let VariantData::Tuple(..) = *self {
407 pub fn is_struct(&self) -> bool {
409 VariantData::Struct(..) => true,
410 VariantData::Tuple(..) => false,
425 fn test_if_let_with_match_on_tail_if_let() {
427 replace_if_let_with_match,
430 pub fn is_struct(&self) -> bool {
431 if let VariantData::Struct(..) = *self {
433 } else if let$0 VariantData::Tuple(..) = *self {
443 pub fn is_struct(&self) -> bool {
444 if let VariantData::Struct(..) = *self {
448 VariantData::Tuple(..) => false,
459 fn special_case_option() {
461 replace_if_let_with_match,
464 fn foo(x: Option<i32>) {
465 $0if let Some(x) = x {
473 fn foo(x: Option<i32>) {
475 Some(x) => println!("{}", x),
476 None => println!("none"),
484 fn special_case_inverted_option() {
486 replace_if_let_with_match,
489 fn foo(x: Option<i32>) {
498 fn foo(x: Option<i32>) {
500 None => println!("none"),
501 Some(_) => println!("some"),
509 fn special_case_result() {
511 replace_if_let_with_match,
514 fn foo(x: Result<i32, ()>) {
523 fn foo(x: Result<i32, ()>) {
525 Ok(x) => println!("{}", x),
526 Err(_) => println!("none"),
534 fn special_case_inverted_result() {
536 replace_if_let_with_match,
539 fn foo(x: Result<i32, ()>) {
540 $0if let Err(x) = x {
548 fn foo(x: Result<i32, ()>) {
550 Err(x) => println!("{}", x),
551 Ok(_) => println!("ok"),
561 replace_if_let_with_match,
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))
577 match path.strip_prefix(root_path) {
579 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
580 Some((*id, rel_path))
593 replace_if_let_with_match,
596 fn foo(x: Result<i32, ()>) {
597 let bar: Result<_, ()> = Ok(Some(1));
598 $0if let Ok(Some(_)) = bar {
606 fn foo(x: Result<i32, ()>) {
607 let bar: Result<_, ()> = Ok(Some(1));
618 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
620 replace_match_with_if_let,
623 pub fn is_struct(&self) -> bool {
625 VariantData::Struct(..) => true,
632 pub fn is_struct(&self) -> bool {
633 if let VariantData::Struct(..) = *self {
644 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
646 replace_match_with_if_let,
650 VariantData::Struct(..) => {
660 if let VariantData::Struct(..) = a {
672 fn replace_match_with_if_let_target() {
674 replace_match_with_if_let,
677 pub fn is_struct(&self) -> bool {
679 VariantData::Struct(..) => true,
685 VariantData::Struct(..) => true,
692 fn special_case_option_match_to_if_let() {
694 replace_match_with_if_let,
697 fn foo(x: Option<i32>) {
699 Some(x) => println!("{}", x),
700 None => println!("none"),
705 fn foo(x: Option<i32>) {
717 fn special_case_result_match_to_if_let() {
719 replace_match_with_if_let,
722 fn foo(x: Result<i32, ()>) {
724 Ok(x) => println!("{}", x),
725 Err(_) => println!("none"),
730 fn foo(x: Result<i32, ()>) {
742 fn nested_indent_match_to_if_let() {
744 replace_match_with_if_let,
748 $0match path.strip_prefix(root_path) {
750 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
751 Some((*id, rel_path))
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))
774 fn replace_match_with_if_let_empty_wildcard_expr() {
776 replace_match_with_if_let,
779 $0match path.strip_prefix(root_path) {
780 Ok(rel_path) => println!("{}", rel_path),
787 if let Ok(rel_path) = path.strip_prefix(root_path) {
788 println!("{}", rel_path)
796 fn replace_match_with_if_let_number_body() {
798 replace_match_with_if_let,
809 if let Err(_) = Ok(()) {
818 fn replace_match_with_if_let_exhaustive() {
820 replace_match_with_if_let,
822 fn print_source(def_source: ModuleSource) {
824 ModuleSource::SourceFile(..) => { println!("source file"); }
825 ModuleSource::Module(..) => { println!("module"); }
830 fn print_source(def_source: ModuleSource) {
831 if let ModuleSource::SourceFile(..) = def_source { println!("source file"); } else { println!("module"); }
838 fn replace_match_with_if_let_prefer_name_bind() {
840 replace_match_with_if_let,
845 Bar(bar) => println!("bar {}", bar),
851 if let Bar(bar) = Foo(0) {
852 println!("bar {}", bar)
858 replace_match_with_if_let,
862 Bar(bar) => println!("bar {}", bar),
869 if let Bar(bar) = Foo(0) {
870 println!("bar {}", bar)
878 fn replace_match_with_if_let_prefer_nonempty_body() {
880 replace_match_with_if_let,
885 Err(err) => eprintln!("{}", err),
891 if let Err(err) = Ok(0) {
898 replace_match_with_if_let,
902 Err(err) => eprintln!("{}", err),
909 if let Err(err) = Ok(0) {
918 fn replace_match_with_if_let_rejects_double_name_bindings() {
919 check_assist_not_applicable(
920 replace_match_with_if_let,
924 Foo(foo) => println!("bar {}", foo),
925 Bar(bar) => println!("bar {}", bar),
933 fn test_replace_match_with_if_let_keeps_unsafe_block() {
935 replace_match_with_if_let,
938 pub fn is_struct(&self) -> bool {
940 VariantData::Struct(..) => true,
941 _ => unsafe { unreachable_unchecked() },
947 pub fn is_struct(&self) -> bool {
948 if let VariantData::Struct(..) = *self {
951 unsafe { unreachable_unchecked() }