3 use ide_db::{ty_filter::TryEnum, RootDatabase};
7 edit::{AstNodeEdit, IndentLevel},
14 utils::{does_pat_match_variant, unwrap_trivial_block},
15 AssistContext, AssistId, AssistKind, Assists,
18 // Assist: replace_if_let_with_match
20 // Replaces `if let` with an else branch with a `match` expression.
23 // enum Action { Move { distance: u32 }, Stop }
25 // fn handle(action: Action) {
26 // $0if let Action::Move { distance } = action {
35 // enum Action { Move { distance: u32 }, Stop }
37 // fn handle(action: Action) {
39 // Action::Move { distance } => foo(distance),
44 pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
46 let cond = if_expr.condition()?;
47 let pat = cond.pat()?;
48 let expr = cond.expr()?;
49 let then_block = if_expr.then_branch()?;
50 let else_block = match if_expr.else_branch()? {
51 ast::ElseBranch::Block(it) => it,
52 ast::ElseBranch::IfExpr(_) => return None,
55 let target = if_expr.syntax().text_range();
57 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
63 let then_block = then_block.reset_indent().indent(IndentLevel(1));
64 let then_expr = unwrap_trivial_block(then_block);
65 make::match_arm(vec![pat.clone()], then_expr)
71 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
73 if does_pat_match_variant(&pat, &it.sad_pattern()) {
79 .unwrap_or_else(|| make::wildcard_pat().into());
80 let else_expr = unwrap_trivial_block(else_block);
81 make::match_arm(vec![pattern], else_expr)
84 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
85 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
88 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
93 // Assist: replace_match_with_if_let
95 // Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
98 // enum Action { Move { distance: u32 }, Stop }
100 // fn handle(action: Action) {
102 // Action::Move { distance } => foo(distance),
109 // enum Action { Move { distance: u32 }, Stop }
111 // fn handle(action: Action) {
112 // if let Action::Move { distance } = action {
119 pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
120 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
121 let mut arms = match_expr.match_arm_list()?.arms();
122 let first_arm = arms.next()?;
123 let second_arm = arms.next()?;
124 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
127 let condition_expr = match_expr.expr()?;
128 let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
130 (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
131 } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
132 (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
137 let target = match_expr.syntax().text_range();
139 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
140 "Replace with if let",
143 let condition = make::condition(condition_expr, Some(if_let_pat));
144 let then_block = match then_expr.reset_indent() {
145 ast::Expr::BlockExpr(block) => block,
146 expr => make::block_expr(iter::empty(), Some(expr)),
148 let else_expr = match else_expr {
149 ast::Expr::BlockExpr(block)
150 if block.statements().count() == 0 && block.tail_expr().is_none() =>
154 ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
157 let if_let_expr = make::expr_if(
160 else_expr.map(|else_expr| {
161 ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
164 .indent(IndentLevel::from_node(match_expr.syntax()));
166 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
171 fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
172 sema.type_of_pat(&pat)
173 .and_then(|ty| TryEnum::from_ty(sema, &ty))
174 .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
175 .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
182 use crate::tests::{check_assist, check_assist_target};
185 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
187 replace_if_let_with_match,
190 pub fn is_struct(&self) -> bool {
191 if $0let VariantData::Struct(..) = *self {
200 pub fn is_struct(&self) -> bool {
202 VariantData::Struct(..) => true,
211 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
213 replace_if_let_with_match,
216 if $0let VariantData::Struct(..) = a {
227 VariantData::Struct(..) => {
239 fn replace_if_let_with_match_target() {
241 replace_if_let_with_match,
244 pub fn is_struct(&self) -> bool {
245 if $0let VariantData::Struct(..) = *self {
252 "if let VariantData::Struct(..) = *self {
261 fn special_case_option() {
263 replace_if_let_with_match,
265 enum Option<T> { Some(T), None }
268 fn foo(x: Option<i32>) {
269 $0if let Some(x) = x {
277 enum Option<T> { Some(T), None }
280 fn foo(x: Option<i32>) {
282 Some(x) => println!("{}", x),
283 None => println!("none"),
291 fn special_case_inverted_option() {
293 replace_if_let_with_match,
295 enum Option<T> { Some(T), None }
298 fn foo(x: Option<i32>) {
307 enum Option<T> { Some(T), None }
310 fn foo(x: Option<i32>) {
312 None => println!("none"),
313 Some(_) => println!("some"),
321 fn special_case_result() {
323 replace_if_let_with_match,
325 enum Result<T, E> { Ok(T), Err(E) }
328 fn foo(x: Result<i32, ()>) {
337 enum Result<T, E> { Ok(T), Err(E) }
340 fn foo(x: Result<i32, ()>) {
342 Ok(x) => println!("{}", x),
343 Err(_) => println!("none"),
351 fn special_case_inverted_result() {
353 replace_if_let_with_match,
355 enum Result<T, E> { Ok(T), Err(E) }
358 fn foo(x: Result<i32, ()>) {
359 $0if let Err(x) = x {
367 enum Result<T, E> { Ok(T), Err(E) }
370 fn foo(x: Result<i32, ()>) {
372 Err(x) => println!("{}", x),
373 Ok(_) => println!("ok"),
383 replace_if_let_with_match,
387 $0if let Ok(rel_path) = path.strip_prefix(root_path) {
388 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
389 Some((*id, rel_path))
399 match path.strip_prefix(root_path) {
401 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
402 Some((*id, rel_path))
413 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
415 replace_match_with_if_let,
418 pub fn is_struct(&self) -> bool {
420 VariantData::Struct(..) => true,
427 pub fn is_struct(&self) -> bool {
428 if let VariantData::Struct(..) = *self {
439 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
441 replace_match_with_if_let,
445 VariantData::Struct(..) => {
455 if let VariantData::Struct(..) = a {
467 fn replace_match_with_if_let_target() {
469 replace_match_with_if_let,
472 pub fn is_struct(&self) -> bool {
474 VariantData::Struct(..) => true,
480 VariantData::Struct(..) => true,
487 fn special_case_option_match_to_if_let() {
489 replace_match_with_if_let,
491 enum Option<T> { Some(T), None }
494 fn foo(x: Option<i32>) {
496 Some(x) => println!("{}", x),
497 None => println!("none"),
502 enum Option<T> { Some(T), None }
505 fn foo(x: Option<i32>) {
517 fn special_case_result_match_to_if_let() {
519 replace_match_with_if_let,
521 enum Result<T, E> { Ok(T), Err(E) }
524 fn foo(x: Result<i32, ()>) {
526 Ok(x) => println!("{}", x),
527 Err(_) => println!("none"),
532 enum Result<T, E> { Ok(T), Err(E) }
535 fn foo(x: Result<i32, ()>) {
547 fn nested_indent_match_to_if_let() {
549 replace_match_with_if_let,
553 $0match path.strip_prefix(root_path) {
555 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
556 Some((*id, rel_path))
566 if let Ok(rel_path) = path.strip_prefix(root_path) {
567 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
568 Some((*id, rel_path))
579 fn replace_match_with_if_let_empty_wildcard_expr() {
581 replace_match_with_if_let,
584 $0match path.strip_prefix(root_path) {
585 Ok(rel_path) => println!("{}", rel_path),
592 if let Ok(rel_path) = path.strip_prefix(root_path) {
593 println!("{}", rel_path)