1 use std
::iter
::{self, successors}
;
6 syntax_helpers
::node_ext
::{is_pattern_cond, single_let}
,
13 edit
::{AstNodeEdit, IndentLevel}
,
16 AstNode
, TextRange
, T
,
20 utils
::{does_nested_pattern, does_pat_match_variant, unwrap_trivial_block}
,
21 AssistContext
, AssistId
, AssistKind
, Assists
,
24 // Assist: replace_if_let_with_match
26 // Replaces a `if let` expression with a `match` expression.
29 // enum Action { Move { distance: u32 }, Stop }
31 // fn handle(action: Action) {
32 // $0if let Action::Move { distance } = action {
41 // enum Action { Move { distance: u32 }, Stop }
43 // fn handle(action: Action) {
45 // Action::Move { distance } => foo(distance),
50 pub(crate) fn replace_if_let_with_match(acc
: &mut Assists
, ctx
: &AssistContext
<'_
>) -> Option
<()> {
51 let if_expr
: ast
::IfExpr
= ctx
.find_node_at_offset()?
;
52 let available_range
= TextRange
::new(
53 if_expr
.syntax().text_range().start(),
54 if_expr
.then_branch()?
.syntax().text_range().start(),
56 let cursor_in_range
= available_range
.contains_range(ctx
.selection_trimmed());
60 let mut else_block
= None
;
61 let if_exprs
= successors(Some(if_expr
.clone()), |expr
| match expr
.else_branch()?
{
62 ast
::ElseBranch
::IfExpr(expr
) => Some(expr
),
63 ast
::ElseBranch
::Block(block
) => {
64 else_block
= Some(block
);
68 let scrutinee_to_be_expr
= if_expr
.condition()?
;
69 let scrutinee_to_be_expr
= match single_let(scrutinee_to_be_expr
.clone()) {
70 Some(cond
) => cond
.expr()?
,
71 None
=> scrutinee_to_be_expr
,
74 let mut pat_seen
= false;
75 let mut cond_bodies
= Vec
::new();
76 for if_expr
in if_exprs
{
77 let cond
= if_expr
.condition()?
;
78 let cond
= match single_let(cond
.clone()) {
80 let pat
= let_
.pat()?
;
81 let expr
= let_
.expr()?
;
82 // FIXME: If one `let` is wrapped in parentheses and the second is not,
84 if scrutinee_to_be_expr
.syntax().text() != expr
.syntax().text() {
85 // Only if all condition expressions are equal we can merge them into a match
91 // Multiple `let`, unsupported.
92 None
if is_pattern_cond(cond
.clone()) => return None
,
93 None
=> Either
::Right(cond
),
95 let body
= if_expr
.then_branch()?
;
96 cond_bodies
.push((cond
, body
));
99 if !pat_seen
&& cond_bodies
.len() != 1 {
100 // Don't offer turning an if (chain) without patterns into a match,
101 // unless its a simple `if cond { .. } (else { .. })`
106 AssistId("replace_if_let_with_match", AssistKind
::RefactorRewrite
),
107 "Replace if let with match",
111 let else_arm
= make_else_arm(ctx
, else_block
, &cond_bodies
);
112 let make_match_arm
= |(pat
, body
): (_
, ast
::BlockExpr
)| {
113 let body
= body
.reset_indent().indent(IndentLevel(1));
115 Either
::Left(pat
) => {
116 make
::match_arm(iter
::once(pat
), None
, unwrap_trivial_block(body
))
118 Either
::Right(_
) if !pat_seen
=> make
::match_arm(
119 iter
::once(make
::literal_pat("true").into()),
121 unwrap_trivial_block(body
),
123 Either
::Right(expr
) => make
::match_arm(
124 iter
::once(make
::wildcard_pat().into()),
126 unwrap_trivial_block(body
),
130 let arms
= cond_bodies
.into_iter().map(make_match_arm
).chain(iter
::once(else_arm
));
131 let match_expr
= make
::expr_match(scrutinee_to_be_expr
, make
::match_arm_list(arms
));
132 match_expr
.indent(IndentLevel
::from_node(if_expr
.syntax()))
135 let has_preceding_if_expr
=
136 if_expr
.syntax().parent().map_or(false, |it
| ast
::IfExpr
::can_cast(it
.kind()));
137 let expr
= if has_preceding_if_expr
{
138 // make sure we replace the `else if let ...` with a block so we don't end up with `else expr`
139 make
::block_expr(None
, Some(match_expr
)).into()
143 edit
.replace_ast
::<ast
::Expr
>(if_expr
.into(), expr
);
149 ctx
: &AssistContext
<'_
>,
150 else_block
: Option
<ast
::BlockExpr
>,
151 conditionals
: &[(Either
<ast
::Pat
, ast
::Expr
>, ast
::BlockExpr
)],
153 let (pattern
, expr
) = if let Some(else_block
) = else_block
{
154 let pattern
= match conditionals
{
155 [(Either
::Right(_
), _
)] => make
::literal_pat("false").into(),
156 [(Either
::Left(pat
), _
)] => match ctx
159 .and_then(|ty
| TryEnum
::from_ty(&ctx
.sema
, &ty
.adjusted()))
162 if does_pat_match_variant(pat
, &it
.sad_pattern()) {
163 it
.happy_pattern_wildcard()
164 } else if does_nested_pattern(pat
) {
165 make
::wildcard_pat().into()
170 None
=> make
::wildcard_pat().into(),
172 _
=> make
::wildcard_pat().into(),
174 (pattern
, unwrap_trivial_block(else_block
))
176 let pattern
= match conditionals
{
177 [(Either
::Right(_
), _
)] => make
::literal_pat("false").into(),
178 _
=> make
::wildcard_pat().into(),
180 (pattern
, make
::expr_unit())
182 make
::match_arm(iter
::once(pattern
), None
, expr
)
185 // Assist: replace_match_with_if_let
187 // Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
190 // enum Action { Move { distance: u32 }, Stop }
192 // fn handle(action: Action) {
194 // Action::Move { distance } => foo(distance),
201 // enum Action { Move { distance: u32 }, Stop }
203 // fn handle(action: Action) {
204 // if let Action::Move { distance } = action {
211 pub(crate) fn replace_match_with_if_let(acc
: &mut Assists
, ctx
: &AssistContext
<'_
>) -> Option
<()> {
212 let match_expr
: ast
::MatchExpr
= ctx
.find_node_at_offset()?
;
214 let mut arms
= match_expr
.match_arm_list()?
.arms();
215 let (first_arm
, second_arm
) = (arms
.next()?
, arms
.next()?
);
216 if arms
.next().is_some() || first_arm
.guard().is_some() || second_arm
.guard().is_some() {
220 let (if_let_pat
, then_expr
, else_expr
) = pick_pattern_and_expr_order(
227 let scrutinee
= match_expr
.expr()?
;
229 let target
= match_expr
.syntax().text_range();
231 AssistId("replace_match_with_if_let", AssistKind
::RefactorRewrite
),
232 "Replace match with if let",
235 fn make_block_expr(expr
: ast
::Expr
) -> ast
::BlockExpr
{
236 // Blocks with modifiers (unsafe, async, etc.) are parsed as BlockExpr, but are
237 // formatted without enclosing braces. If we encounter such block exprs,
238 // wrap them in another BlockExpr.
240 ast
::Expr
::BlockExpr(block
) if block
.modifier().is_none() => block
,
241 expr
=> make
::block_expr(iter
::empty(), Some(expr
)),
245 let condition
= match if_let_pat
{
246 ast
::Pat
::LiteralPat(p
)
247 if p
.literal().map_or(false, |it
| it
.token().kind() == T
![true]) =>
251 ast
::Pat
::LiteralPat(p
)
252 if p
.literal().map_or(false, |it
| it
.token().kind() == T
![false]) =>
254 make
::expr_prefix(T
![!], scrutinee
)
256 _
=> make
::expr_let(if_let_pat
, scrutinee
).into(),
258 let then_block
= make_block_expr(then_expr
.reset_indent());
259 let else_expr
= if is_empty_expr(&else_expr
) { None }
else { Some(else_expr) }
;
260 let if_let_expr
= make
::expr_if(
263 else_expr
.map(make_block_expr
).map(ast
::ElseBranch
::Block
),
265 .indent(IndentLevel
::from_node(match_expr
.syntax()));
267 edit
.replace_ast
::<ast
::Expr
>(match_expr
.into(), if_let_expr
);
272 /// Pick the pattern for the if let condition and return the expressions for the `then` body and `else` body in that order.
273 fn pick_pattern_and_expr_order(
274 sema
: &hir
::Semantics
<'_
, RootDatabase
>,
279 ) -> Option
<(ast
::Pat
, ast
::Expr
, ast
::Expr
)> {
280 let res
= match (pat
, pat2
) {
281 (ast
::Pat
::WildcardPat(_
), _
) => return None
,
282 (pat
, ast
::Pat
::WildcardPat(_
)) => (pat
, expr
, expr2
),
283 (pat
, _
) if is_empty_expr(&expr2
) => (pat
, expr
, expr2
),
284 (_
, pat
) if is_empty_expr(&expr
) => (pat
, expr2
, expr
),
285 (pat
, pat2
) => match (binds_name(sema
, &pat
), binds_name(sema
, &pat2
)) {
286 (true, true) => return None
,
287 (true, false) => (pat
, expr
, expr2
),
288 (false, true) => (pat2
, expr2
, expr
),
289 _
if is_sad_pat(sema
, &pat
) => (pat2
, expr2
, expr
),
290 (false, false) => (pat
, expr
, expr2
),
296 fn is_empty_expr(expr
: &ast
::Expr
) -> bool
{
298 ast
::Expr
::BlockExpr(expr
) => match expr
.stmt_list() {
299 Some(it
) => it
.statements().next().is_none() && it
.tail_expr().is_none(),
302 ast
::Expr
::TupleExpr(expr
) => expr
.fields().next().is_none(),
307 fn binds_name(sema
: &hir
::Semantics
<'_
, RootDatabase
>, pat
: &ast
::Pat
) -> bool
{
308 let binds_name_v
= |pat
| binds_name(sema
, &pat
);
310 ast
::Pat
::IdentPat(pat
) => !matches
!(
311 pat
.name().and_then(|name
| NameClass
::classify(sema
, &name
)),
312 Some(NameClass
::ConstReference(_
))
314 ast
::Pat
::MacroPat(_
) => true,
315 ast
::Pat
::OrPat(pat
) => pat
.pats().any(binds_name_v
),
316 ast
::Pat
::SlicePat(pat
) => pat
.pats().any(binds_name_v
),
317 ast
::Pat
::TuplePat(it
) => it
.fields().any(binds_name_v
),
318 ast
::Pat
::TupleStructPat(it
) => it
.fields().any(binds_name_v
),
319 ast
::Pat
::RecordPat(it
) => it
320 .record_pat_field_list()
321 .map_or(false, |rpfl
| rpfl
.fields().flat_map(|rpf
| rpf
.pat()).any(binds_name_v
)),
322 ast
::Pat
::RefPat(pat
) => pat
.pat().map_or(false, binds_name_v
),
323 ast
::Pat
::BoxPat(pat
) => pat
.pat().map_or(false, binds_name_v
),
324 ast
::Pat
::ParenPat(pat
) => pat
.pat().map_or(false, binds_name_v
),
329 fn is_sad_pat(sema
: &hir
::Semantics
<'_
, RootDatabase
>, pat
: &ast
::Pat
) -> bool
{
330 sema
.type_of_pat(pat
)
331 .and_then(|ty
| TryEnum
::from_ty(sema
, &ty
.adjusted()))
332 .map_or(false, |it
| does_pat_match_variant(pat
, &it
.sad_pattern()))
339 use crate::tests
::{check_assist, check_assist_not_applicable, check_assist_target}
;
342 fn test_if_let_with_match_unapplicable_for_simple_ifs() {
343 check_assist_not_applicable(
344 replace_if_let_with_match
,
347 if $0true {} else if false {} else {}
354 fn test_if_with_match_no_else() {
356 replace_if_let_with_match
,
358 pub fn foo(foo: bool) {
365 pub fn foo(foo: bool) {
378 fn test_if_with_match_with_else() {
380 replace_if_let_with_match
,
382 pub fn foo(foo: bool) {
391 pub fn foo(foo: bool) {
406 fn test_if_let_with_match_no_else() {
408 replace_if_let_with_match
,
412 if $0let VariantData::Struct(..) = *self {
422 VariantData::Struct(..) => {
434 fn test_if_let_with_match_available_range_left() {
435 check_assist_not_applicable(
436 replace_if_let_with_match
,
440 $0 if let VariantData::Struct(..) = *self {
450 fn test_if_let_with_match_available_range_right() {
451 check_assist_not_applicable(
452 replace_if_let_with_match
,
456 if let VariantData::Struct(..) = *self {$0
466 fn test_if_let_with_match_let_chain() {
467 check_assist_not_applicable(
468 replace_if_let_with_match
,
471 if $0let true = true && let Some(1) = None {}
478 fn test_if_let_with_match_basic() {
480 replace_if_let_with_match
,
483 pub fn is_struct(&self) -> bool {
484 if $0let VariantData::Struct(..) = *self {
486 } else if let VariantData::Tuple(..) = *self {
500 pub fn is_struct(&self) -> bool {
502 VariantData::Struct(..) => true,
503 VariantData::Tuple(..) => false,
518 fn test_if_let_with_match_on_tail_if_let() {
520 replace_if_let_with_match
,
523 pub fn is_struct(&self) -> bool {
524 if let VariantData::Struct(..) = *self {
526 } else if let$0 VariantData::Tuple(..) = *self {
536 pub fn is_struct(&self) -> bool {
537 if let VariantData::Struct(..) = *self {
541 VariantData::Tuple(..) => false,
552 fn special_case_option() {
554 replace_if_let_with_match
,
557 fn foo(x: Option<i32>) {
558 $0if let Some(x) = x {
566 fn foo(x: Option<i32>) {
568 Some(x) => println!("{}", x),
569 None => println!("none"),
577 fn special_case_inverted_option() {
579 replace_if_let_with_match
,
582 fn foo(x: Option<i32>) {
591 fn foo(x: Option<i32>) {
593 None => println!("none"),
594 Some(_) => println!("some"),
602 fn special_case_result() {
604 replace_if_let_with_match
,
607 fn foo(x: Result<i32, ()>) {
616 fn foo(x: Result<i32, ()>) {
618 Ok(x) => println!("{}", x),
619 Err(_) => println!("none"),
627 fn special_case_inverted_result() {
629 replace_if_let_with_match
,
632 fn foo(x: Result<i32, ()>) {
633 $0if let Err(x) = x {
641 fn foo(x: Result<i32, ()>) {
643 Err(x) => println!("{}", x),
644 Ok(_) => println!("ok"),
654 replace_if_let_with_match
,
658 $0if let Ok(rel_path) = path.strip_prefix(root_path) {
659 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
660 Some((*id, rel_path))
670 match path.strip_prefix(root_path) {
672 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
673 Some((*id, rel_path))
686 replace_if_let_with_match
,
689 fn foo(x: Result<i32, ()>) {
690 let bar: Result<_, ()> = Ok(Some(1));
691 $0if let Ok(Some(_)) = bar {
699 fn foo(x: Result<i32, ()>) {
700 let bar: Result<_, ()> = Ok(Some(1));
711 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
713 replace_match_with_if_let
,
716 pub fn is_struct(&self) -> bool {
718 VariantData::Struct(..) => true,
725 pub fn is_struct(&self) -> bool {
726 if let VariantData::Struct(..) = *self {
737 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
739 replace_match_with_if_let
,
743 VariantData::Struct(..) => {
753 if let VariantData::Struct(..) = a {
765 fn replace_match_with_if_let_target() {
767 replace_match_with_if_let
,
770 pub fn is_struct(&self) -> bool {
772 VariantData::Struct(..) => true,
778 VariantData::Struct(..) => true,
785 fn special_case_option_match_to_if_let() {
787 replace_match_with_if_let
,
790 fn foo(x: Option<i32>) {
792 Some(x) => println!("{}", x),
793 None => println!("none"),
798 fn foo(x: Option<i32>) {
810 fn special_case_result_match_to_if_let() {
812 replace_match_with_if_let
,
815 fn foo(x: Result<i32, ()>) {
817 Ok(x) => println!("{}", x),
818 Err(_) => println!("none"),
823 fn foo(x: Result<i32, ()>) {
835 fn nested_indent_match_to_if_let() {
837 replace_match_with_if_let
,
841 $0match path.strip_prefix(root_path) {
843 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
844 Some((*id, rel_path))
854 if let Ok(rel_path) = path.strip_prefix(root_path) {
855 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
856 Some((*id, rel_path))
867 fn replace_match_with_if_let_empty_wildcard_expr() {
869 replace_match_with_if_let
,
872 $0match path.strip_prefix(root_path) {
873 Ok(rel_path) => println!("{}", rel_path),
880 if let Ok(rel_path) = path.strip_prefix(root_path) {
881 println!("{}", rel_path)
889 fn replace_match_with_if_let_number_body() {
891 replace_match_with_if_let
,
902 if let Err(_) = Ok(()) {
911 fn replace_match_with_if_let_exhaustive() {
913 replace_match_with_if_let
,
915 fn print_source(def_source: ModuleSource) {
917 ModuleSource::SourceFile(..) => { println!("source file"); }
918 ModuleSource::Module(..) => { println!("module"); }
923 fn print_source(def_source: ModuleSource) {
924 if let ModuleSource::SourceFile(..) = def_source { println!("source file"); } else { println!("module"); }
931 fn replace_match_with_if_let_prefer_name_bind() {
933 replace_match_with_if_let
,
938 Bar(bar) => println!("bar {}", bar),
944 if let Bar(bar) = Foo(0) {
945 println!("bar {}", bar)
951 replace_match_with_if_let
,
955 Bar(bar) => println!("bar {}", bar),
962 if let Bar(bar) = Foo(0) {
963 println!("bar {}", bar)
971 fn replace_match_with_if_let_prefer_nonempty_body() {
973 replace_match_with_if_let
,
978 Err(err) => eprintln!("{}", err),
984 if let Err(err) = Ok(0) {
991 replace_match_with_if_let
,
995 Err(err) => eprintln!("{}", err),
1002 if let Err(err) = Ok(0) {
1003 eprintln!("{}", err)
1011 fn replace_match_with_if_let_rejects_double_name_bindings() {
1012 check_assist_not_applicable(
1013 replace_match_with_if_let
,
1017 Foo(foo) => println!("bar {}", foo),
1018 Bar(bar) => println!("bar {}", bar),
1026 fn test_replace_match_with_if_let_keeps_unsafe_block() {
1028 replace_match_with_if_let
,
1031 pub fn is_struct(&self) -> bool {
1033 VariantData::Struct(..) => true,
1034 _ => unsafe { unreachable_unchecked() },
1040 pub fn is_struct(&self) -> bool {
1041 if let VariantData::Struct(..) = *self {
1044 unsafe { unreachable_unchecked() }
1052 fn test_replace_match_with_if_let_forces_else() {
1054 replace_match_with_if_let
,
1076 fn test_replace_match_with_if_bool() {
1078 replace_match_with_if_let
,
1098 replace_match_with_if_let
,
1116 replace_match_with_if_let
,