Skip to content

Commit 6df91a8

Browse files
bors[bot]Veykril
andauthored
Merge #6731
6731: Add replace_match_with_if_let assist r=matklad a=Veykril Basically the counterpart to `replace_if_let_with_match`, I personally sometimes want to replace matches like ```rust match foo { pat => expr, _ => (), } ``` into the corresponding ```rust if let pat = foo { expr } ``` which is the main reasoning behind this. I put this into the same file as `replace_if_let_with_match` because the are complementing each other and I would probably rename the file to something like `replace_if_let_match` but I didn't do that for now because I was unsure whether git would still view this as a rename or not due to the amount of changes in the file so that the diff is still properly visible for now. Co-authored-by: Lukas Wirth <[email protected]>
2 parents 403ed48 + 44c76d6 commit 6df91a8

File tree

7 files changed

+319
-5
lines changed

7 files changed

+319
-5
lines changed

crates/assists/src/handlers/early_return.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
112112
let then_branch =
113113
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114114
let cond = invert_boolean_expression(cond_expr);
115-
make::expr_if(make::condition(cond, None), then_branch)
115+
make::expr_if(make::condition(cond, None), then_branch, None)
116116
.indent(if_indent_level)
117117
};
118118
replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)

crates/assists/src/handlers/move_guard.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
4242
let if_expr = make::expr_if(
4343
make::condition(guard_condition, None),
4444
make::block_expr(None, Some(arm_expr.clone())),
45+
None,
4546
)
4647
.indent(arm_expr.indent_level());
4748

crates/assists/src/handlers/replace_if_let_with_match.rs

Lines changed: 276 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use std::iter;
2+
3+
use ide_db::{ty_filter::TryEnum, RootDatabase};
14
use syntax::{
25
ast::{
36
self,
@@ -8,7 +11,6 @@ use syntax::{
811
};
912

1013
use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
11-
use ide_db::ty_filter::TryEnum;
1214

1315
// Assist: replace_if_let_with_match
1416
//
@@ -79,6 +81,91 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
7981
)
8082
}
8183

84+
// Assist: replace_match_with_if_let
85+
//
86+
// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
87+
//
88+
// ```
89+
// enum Action { Move { distance: u32 }, Stop }
90+
//
91+
// fn handle(action: Action) {
92+
// <|>match action {
93+
// Action::Move { distance } => foo(distance),
94+
// _ => bar(),
95+
// }
96+
// }
97+
// ```
98+
// ->
99+
// ```
100+
// enum Action { Move { distance: u32 }, Stop }
101+
//
102+
// fn handle(action: Action) {
103+
// if let Action::Move { distance } = action {
104+
// foo(distance)
105+
// } else {
106+
// bar()
107+
// }
108+
// }
109+
// ```
110+
pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
111+
let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
112+
let mut arms = match_expr.match_arm_list()?.arms();
113+
let first_arm = arms.next()?;
114+
let second_arm = arms.next()?;
115+
if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
116+
return None;
117+
}
118+
let condition_expr = match_expr.expr()?;
119+
let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
120+
{
121+
(second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
122+
} else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
123+
(first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
124+
} else {
125+
return None;
126+
};
127+
128+
let target = match_expr.syntax().text_range();
129+
acc.add(
130+
AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
131+
"Replace with if let",
132+
target,
133+
move |edit| {
134+
let condition = make::condition(condition_expr, Some(if_let_pat));
135+
let then_block = match then_expr.reset_indent() {
136+
ast::Expr::BlockExpr(block) => block,
137+
expr => make::block_expr(iter::empty(), Some(expr)),
138+
};
139+
let else_expr = match else_expr {
140+
ast::Expr::BlockExpr(block)
141+
if block.statements().count() == 0 && block.expr().is_none() =>
142+
{
143+
None
144+
}
145+
ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
146+
expr => Some(expr),
147+
};
148+
let if_let_expr = make::expr_if(
149+
condition,
150+
then_block,
151+
else_expr.map(|else_expr| {
152+
ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
153+
}),
154+
)
155+
.indent(IndentLevel::from_node(match_expr.syntax()));
156+
157+
edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
158+
},
159+
)
160+
}
161+
162+
fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
163+
sema.type_of_pat(&pat)
164+
.and_then(|ty| TryEnum::from_ty(sema, &ty))
165+
.map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
166+
.unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
167+
}
168+
82169
#[cfg(test)]
83170
mod tests {
84171
use super::*;
@@ -249,6 +336,194 @@ fn main() {
249336
}
250337
}
251338
}
339+
"#,
340+
)
341+
}
342+
343+
#[test]
344+
fn test_replace_match_with_if_let_unwraps_simple_expressions() {
345+
check_assist(
346+
replace_match_with_if_let,
347+
r#"
348+
impl VariantData {
349+
pub fn is_struct(&self) -> bool {
350+
<|>match *self {
351+
VariantData::Struct(..) => true,
352+
_ => false,
353+
}
354+
}
355+
} "#,
356+
r#"
357+
impl VariantData {
358+
pub fn is_struct(&self) -> bool {
359+
if let VariantData::Struct(..) = *self {
360+
true
361+
} else {
362+
false
363+
}
364+
}
365+
} "#,
366+
)
367+
}
368+
369+
#[test]
370+
fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
371+
check_assist(
372+
replace_match_with_if_let,
373+
r#"
374+
fn foo() {
375+
<|>match a {
376+
VariantData::Struct(..) => {
377+
bar(
378+
123
379+
)
380+
}
381+
_ => false,
382+
}
383+
} "#,
384+
r#"
385+
fn foo() {
386+
if let VariantData::Struct(..) = a {
387+
bar(
388+
123
389+
)
390+
} else {
391+
false
392+
}
393+
} "#,
394+
)
395+
}
396+
397+
#[test]
398+
fn replace_match_with_if_let_target() {
399+
check_assist_target(
400+
replace_match_with_if_let,
401+
r#"
402+
impl VariantData {
403+
pub fn is_struct(&self) -> bool {
404+
<|>match *self {
405+
VariantData::Struct(..) => true,
406+
_ => false,
407+
}
408+
}
409+
} "#,
410+
r#"match *self {
411+
VariantData::Struct(..) => true,
412+
_ => false,
413+
}"#,
414+
);
415+
}
416+
417+
#[test]
418+
fn special_case_option_match_to_if_let() {
419+
check_assist(
420+
replace_match_with_if_let,
421+
r#"
422+
enum Option<T> { Some(T), None }
423+
use Option::*;
424+
425+
fn foo(x: Option<i32>) {
426+
<|>match x {
427+
Some(x) => println!("{}", x),
428+
None => println!("none"),
429+
}
430+
}
431+
"#,
432+
r#"
433+
enum Option<T> { Some(T), None }
434+
use Option::*;
435+
436+
fn foo(x: Option<i32>) {
437+
if let Some(x) = x {
438+
println!("{}", x)
439+
} else {
440+
println!("none")
441+
}
442+
}
443+
"#,
444+
);
445+
}
446+
447+
#[test]
448+
fn special_case_result_match_to_if_let() {
449+
check_assist(
450+
replace_match_with_if_let,
451+
r#"
452+
enum Result<T, E> { Ok(T), Err(E) }
453+
use Result::*;
454+
455+
fn foo(x: Result<i32, ()>) {
456+
<|>match x {
457+
Ok(x) => println!("{}", x),
458+
Err(_) => println!("none"),
459+
}
460+
}
461+
"#,
462+
r#"
463+
enum Result<T, E> { Ok(T), Err(E) }
464+
use Result::*;
465+
466+
fn foo(x: Result<i32, ()>) {
467+
if let Ok(x) = x {
468+
println!("{}", x)
469+
} else {
470+
println!("none")
471+
}
472+
}
473+
"#,
474+
);
475+
}
476+
477+
#[test]
478+
fn nested_indent_match_to_if_let() {
479+
check_assist(
480+
replace_match_with_if_let,
481+
r#"
482+
fn main() {
483+
if true {
484+
<|>match path.strip_prefix(root_path) {
485+
Ok(rel_path) => {
486+
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
487+
Some((*id, rel_path))
488+
}
489+
_ => None,
490+
}
491+
}
492+
}
493+
"#,
494+
r#"
495+
fn main() {
496+
if true {
497+
if let Ok(rel_path) = path.strip_prefix(root_path) {
498+
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
499+
Some((*id, rel_path))
500+
} else {
501+
None
502+
}
503+
}
504+
}
505+
"#,
506+
)
507+
}
508+
509+
#[test]
510+
fn replace_match_with_if_let_empty_wildcard_expr() {
511+
check_assist(
512+
replace_match_with_if_let,
513+
r#"
514+
fn main() {
515+
<|>match path.strip_prefix(root_path) {
516+
Ok(rel_path) => println!("{}", rel_path),
517+
_ => (),
518+
}
519+
}
520+
"#,
521+
r#"
522+
fn main() {
523+
if let Ok(rel_path) = path.strip_prefix(root_path) {
524+
println!("{}", rel_path)
525+
}
526+
}
252527
"#,
253528
)
254529
}

crates/assists/src/handlers/replace_let_with_if_let.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
6060
};
6161
let block =
6262
make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
63-
let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
63+
let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, None);
6464
let stmt = make::expr_stmt(if_);
6565

6666
let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap();

crates/assists/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ mod handlers {
209209
reorder_fields::reorder_fields,
210210
replace_derive_with_manual_impl::replace_derive_with_manual_impl,
211211
replace_if_let_with_match::replace_if_let_with_match,
212+
replace_if_let_with_match::replace_match_with_if_let,
212213
replace_impl_trait_with_generic::replace_impl_trait_with_generic,
213214
replace_let_with_if_let::replace_let_with_if_let,
214215
replace_qualified_name_with_use::replace_qualified_name_with_use,

crates/assists/src/tests/generated.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,34 @@ fn compute() -> Option<i32> { None }
889889
)
890890
}
891891

892+
#[test]
893+
fn doctest_replace_match_with_if_let() {
894+
check_doc_test(
895+
"replace_match_with_if_let",
896+
r#####"
897+
enum Action { Move { distance: u32 }, Stop }
898+
899+
fn handle(action: Action) {
900+
<|>match action {
901+
Action::Move { distance } => foo(distance),
902+
_ => bar(),
903+
}
904+
}
905+
"#####,
906+
r#####"
907+
enum Action { Move { distance: u32 }, Stop }
908+
909+
fn handle(action: Action) {
910+
if let Action::Move { distance } = action {
911+
foo(distance)
912+
} else {
913+
bar()
914+
}
915+
}
916+
"#####,
917+
)
918+
}
919+
892920
#[test]
893921
fn doctest_replace_qualified_name_with_use() {
894922
check_doc_test(

crates/syntax/src/ast/make.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,17 @@ pub fn expr_return() -> ast::Expr {
171171
pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
172172
expr_from_text(&format!("match {} {}", expr, match_arm_list))
173173
}
174-
pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr {
175-
expr_from_text(&format!("if {} {}", condition, then_branch))
174+
pub fn expr_if(
175+
condition: ast::Condition,
176+
then_branch: ast::BlockExpr,
177+
else_branch: Option<ast::ElseBranch>,
178+
) -> ast::Expr {
179+
let else_branch = match else_branch {
180+
Some(ast::ElseBranch::Block(block)) => format!("else {}", block),
181+
Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {}", if_expr),
182+
None => String::new(),
183+
};
184+
expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch))
176185
}
177186
pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
178187
let token = token(op);

0 commit comments

Comments
 (0)