1 use std::iter::{self, successors};
4 use ide_db::{defs::NameClass, 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 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.frange.range);
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()?.expr()?;
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() {
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
79 None => Either::Right(expr),
81 let body = if_expr.then_branch()?;
82 cond_bodies.push((cond, body));
86 // Don't offer turning an if (chain) without patterns into a match
91 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
92 "Replace if let with match",
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));
100 Either::Left(pat) => {
101 make::match_arm(iter::once(pat), None, unwrap_trivial_block(body))
103 Either::Right(expr) => make::match_arm(
104 iter::once(make::wildcard_pat().into()),
106 unwrap_trivial_block(body),
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()))
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()
123 edit.replace_ast::<ast::Expr>(if_expr.into(), expr);
130 else_block: Option<ast::BlockExpr>,
131 conditionals: &[(Either<ast::Pat, ast::Expr>, ast::BlockExpr)],
133 if let Some(else_block) = else_block {
134 let pattern = if let [(Either::Left(pat), _)] = conditionals {
137 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
142 let pattern = match pattern {
144 if does_pat_match_variant(pat, &it.sad_pattern()) {
150 None => make::wildcard_pat().into(),
152 make::match_arm(iter::once(pattern), None, unwrap_trivial_block(else_block))
154 make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit())
158 // Assist: replace_match_with_if_let
160 // Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
163 // enum Action { Move { distance: u32 }, Stop }
165 // fn handle(action: Action) {
167 // Action::Move { distance } => foo(distance),
174 // enum Action { Move { distance: u32 }, Stop }
176 // fn handle(action: Action) {
177 // if let Action::Move { distance } = action {
184 pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
185 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
187 let mut arms = match_expr.match_arm_list()?.arms();
188 let (first_arm, second_arm) = (arms.next()?, arms.next()?);
189 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
193 let (if_let_pat, then_expr, else_expr) = pick_pattern_and_expr_order(
200 let scrutinee = match_expr.expr()?;
202 let target = match_expr.syntax().text_range();
204 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
205 "Replace match with if let",
208 let condition = make::condition(scrutinee, Some(if_let_pat));
209 let then_block = match then_expr.reset_indent() {
210 ast::Expr::BlockExpr(block) => block,
211 expr => make::block_expr(iter::empty(), Some(expr)),
213 let else_expr = match else_expr {
214 ast::Expr::BlockExpr(block) if block.is_empty() => None,
215 ast::Expr::TupleExpr(tuple) if tuple.fields().next().is_none() => None,
218 let if_let_expr = make::expr_if(
222 .map(|expr| match expr {
223 ast::Expr::BlockExpr(block) => block,
224 expr => (make::block_expr(iter::empty(), Some(expr))),
226 .map(ast::ElseBranch::Block),
228 .indent(IndentLevel::from_node(match_expr.syntax()));
230 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
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>,
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),
258 fn is_empty_expr(expr: &ast::Expr) -> bool {
260 ast::Expr::BlockExpr(expr) => expr.is_empty(),
261 ast::Expr::TupleExpr(expr) => expr.fields().next().is_none(),
266 fn binds_name(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
267 let binds_name_v = |pat| binds_name(sema, &pat);
269 ast::Pat::IdentPat(pat) => !matches!(
270 pat.name().and_then(|name| NameClass::classify(sema, &name)),
271 Some(NameClass::ConstReference(_))
273 ast::Pat::MacroPat(_) => true,
274 ast::Pat::OrPat(pat) => pat.pats().any(binds_name_v),
275 ast::Pat::SlicePat(pat) => pat.pats().any(binds_name_v),
276 ast::Pat::TuplePat(it) => it.fields().any(binds_name_v),
277 ast::Pat::TupleStructPat(it) => it.fields().any(binds_name_v),
278 ast::Pat::RecordPat(it) => it
279 .record_pat_field_list()
280 .map_or(false, |rpfl| rpfl.fields().flat_map(|rpf| rpf.pat()).any(binds_name_v)),
281 ast::Pat::RefPat(pat) => pat.pat().map_or(false, binds_name_v),
282 ast::Pat::BoxPat(pat) => pat.pat().map_or(false, binds_name_v),
283 ast::Pat::ParenPat(pat) => pat.pat().map_or(false, binds_name_v),
288 fn is_sad_pat(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
289 sema.type_of_pat(pat)
290 .and_then(|ty| TryEnum::from_ty(sema, &ty.adjusted()))
291 .map_or(false, |it| does_pat_match_variant(pat, &it.sad_pattern()))
298 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
301 fn test_if_let_with_match_unapplicable_for_simple_ifs() {
302 check_assist_not_applicable(
303 replace_if_let_with_match,
306 if $0true {} else if false {} else {}
313 fn test_if_let_with_match_no_else() {
315 replace_if_let_with_match,
319 if $0let VariantData::Struct(..) = *self {
329 VariantData::Struct(..) => {
341 fn test_if_let_with_match_available_range_left() {
342 check_assist_not_applicable(
343 replace_if_let_with_match,
347 $0 if let VariantData::Struct(..) = *self {
357 fn test_if_let_with_match_available_range_right() {
358 check_assist_not_applicable(
359 replace_if_let_with_match,
363 if let VariantData::Struct(..) = *self {$0
373 fn test_if_let_with_match_basic() {
375 replace_if_let_with_match,
378 pub fn is_struct(&self) -> bool {
379 if $0let VariantData::Struct(..) = *self {
381 } else if let VariantData::Tuple(..) = *self {
395 pub fn is_struct(&self) -> bool {
397 VariantData::Struct(..) => true,
398 VariantData::Tuple(..) => false,
413 fn test_if_let_with_match_on_tail_if_let() {
415 replace_if_let_with_match,
418 pub fn is_struct(&self) -> bool {
419 if let VariantData::Struct(..) = *self {
421 } else if let$0 VariantData::Tuple(..) = *self {
431 pub fn is_struct(&self) -> bool {
432 if let VariantData::Struct(..) = *self {
436 VariantData::Tuple(..) => false,
447 fn special_case_option() {
449 replace_if_let_with_match,
452 fn foo(x: Option<i32>) {
453 $0if let Some(x) = x {
461 fn foo(x: Option<i32>) {
463 Some(x) => println!("{}", x),
464 None => println!("none"),
472 fn special_case_inverted_option() {
474 replace_if_let_with_match,
477 fn foo(x: Option<i32>) {
486 fn foo(x: Option<i32>) {
488 None => println!("none"),
489 Some(_) => println!("some"),
497 fn special_case_result() {
499 replace_if_let_with_match,
502 fn foo(x: Result<i32, ()>) {
511 fn foo(x: Result<i32, ()>) {
513 Ok(x) => println!("{}", x),
514 Err(_) => println!("none"),
522 fn special_case_inverted_result() {
524 replace_if_let_with_match,
527 fn foo(x: Result<i32, ()>) {
528 $0if let Err(x) = x {
536 fn foo(x: Result<i32, ()>) {
538 Err(x) => println!("{}", x),
539 Ok(_) => println!("ok"),
549 replace_if_let_with_match,
553 $0if let Ok(rel_path) = path.strip_prefix(root_path) {
554 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
555 Some((*id, rel_path))
565 match path.strip_prefix(root_path) {
567 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
568 Some((*id, rel_path))
579 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
581 replace_match_with_if_let,
584 pub fn is_struct(&self) -> bool {
586 VariantData::Struct(..) => true,
593 pub fn is_struct(&self) -> bool {
594 if let VariantData::Struct(..) = *self {
605 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
607 replace_match_with_if_let,
611 VariantData::Struct(..) => {
621 if let VariantData::Struct(..) = a {
633 fn replace_match_with_if_let_target() {
635 replace_match_with_if_let,
638 pub fn is_struct(&self) -> bool {
640 VariantData::Struct(..) => true,
646 VariantData::Struct(..) => true,
653 fn special_case_option_match_to_if_let() {
655 replace_match_with_if_let,
658 fn foo(x: Option<i32>) {
660 Some(x) => println!("{}", x),
661 None => println!("none"),
666 fn foo(x: Option<i32>) {
678 fn special_case_result_match_to_if_let() {
680 replace_match_with_if_let,
683 fn foo(x: Result<i32, ()>) {
685 Ok(x) => println!("{}", x),
686 Err(_) => println!("none"),
691 fn foo(x: Result<i32, ()>) {
703 fn nested_indent_match_to_if_let() {
705 replace_match_with_if_let,
709 $0match path.strip_prefix(root_path) {
711 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
712 Some((*id, rel_path))
722 if let Ok(rel_path) = path.strip_prefix(root_path) {
723 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
724 Some((*id, rel_path))
735 fn replace_match_with_if_let_empty_wildcard_expr() {
737 replace_match_with_if_let,
740 $0match path.strip_prefix(root_path) {
741 Ok(rel_path) => println!("{}", rel_path),
748 if let Ok(rel_path) = path.strip_prefix(root_path) {
749 println!("{}", rel_path)
757 fn replace_match_with_if_let_number_body() {
759 replace_match_with_if_let,
770 if let Err(_) = Ok(()) {
779 fn replace_match_with_if_let_exhaustive() {
781 replace_match_with_if_let,
783 fn print_source(def_source: ModuleSource) {
785 ModuleSource::SourceFile(..) => { println!("source file"); }
786 ModuleSource::Module(..) => { println!("module"); }
791 fn print_source(def_source: ModuleSource) {
792 if let ModuleSource::SourceFile(..) = def_source { println!("source file"); } else { println!("module"); }
799 fn replace_match_with_if_let_prefer_name_bind() {
801 replace_match_with_if_let,
806 Bar(bar) => println!("bar {}", bar),
812 if let Bar(bar) = Foo(0) {
813 println!("bar {}", bar)
819 replace_match_with_if_let,
823 Bar(bar) => println!("bar {}", bar),
830 if let Bar(bar) = Foo(0) {
831 println!("bar {}", bar)
839 fn replace_match_with_if_let_prefer_nonempty_body() {
841 replace_match_with_if_let,
846 Err(err) => eprintln!("{}", err),
852 if let Err(err) = Ok(0) {
859 replace_match_with_if_let,
863 Err(err) => eprintln!("{}", err),
870 if let Err(err) = Ok(0) {
879 fn replace_match_with_if_let_rejects_double_name_bindings() {
880 check_assist_not_applicable(
881 replace_match_with_if_let,
885 Foo(foo) => println!("bar {}", foo),
886 Bar(bar) => println!("bar {}", bar),