Skip to content

Commit 79a578a

Browse files
committed
Auto merge of rust-lang#13005 - pocket7878:convert-two-arm-bool-match-to-matches-macro, r=jonas-schievink
feature: Assist to turn match into matches! invocation Resolves rust-lang#12510 This PR adds an assist, which convert 2-arm match that evaluates to a boolean into the equivalent matches! invocation.
2 parents cf05b7d + 7464b6d commit 79a578a

File tree

3 files changed

+316
-0
lines changed

3 files changed

+316
-0
lines changed
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
use syntax::ast::{self, AstNode};
2+
3+
use crate::{AssistContext, AssistId, AssistKind, Assists};
4+
5+
// Assist: convert_two_arm_bool_match_to_matches_macro
6+
//
7+
// Convert 2-arm match that evaluates to a boolean into the equivalent matches! invocation.
8+
//
9+
// ```
10+
// fn main() {
11+
// match scrutinee$0 {
12+
// Some(val) if val.cond() => true,
13+
// _ => false,
14+
// }
15+
// }
16+
// ```
17+
// ->
18+
// ```
19+
// fn main() {
20+
// matches!(scrutinee, Some(val) if val.cond())
21+
// }
22+
// ```
23+
pub(crate) fn convert_two_arm_bool_match_to_matches_macro(
24+
acc: &mut Assists,
25+
ctx: &AssistContext<'_>,
26+
) -> Option<()> {
27+
let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
28+
let match_arm_list = match_expr.match_arm_list()?;
29+
let mut arms = match_arm_list.arms();
30+
let first_arm = arms.next()?;
31+
let second_arm = arms.next()?;
32+
if arms.next().is_some() {
33+
cov_mark::hit!(non_two_arm_match);
34+
return None;
35+
}
36+
let first_arm_expr = first_arm.expr();
37+
let second_arm_expr = second_arm.expr();
38+
39+
let invert_matches = if is_bool_literal_expr(&first_arm_expr, true)
40+
&& is_bool_literal_expr(&second_arm_expr, false)
41+
{
42+
false
43+
} else if is_bool_literal_expr(&first_arm_expr, false)
44+
&& is_bool_literal_expr(&second_arm_expr, true)
45+
{
46+
true
47+
} else {
48+
cov_mark::hit!(non_invert_bool_literal_arms);
49+
return None;
50+
};
51+
52+
let target_range = ctx.sema.original_range(match_expr.syntax()).range;
53+
let expr = match_expr.expr()?;
54+
55+
acc.add(
56+
AssistId("convert_two_arm_bool_match_to_matches_macro", AssistKind::RefactorRewrite),
57+
"Convert to matches!",
58+
target_range,
59+
|builder| {
60+
let mut arm_str = String::new();
61+
if let Some(ref pat) = first_arm.pat() {
62+
arm_str += &pat.to_string();
63+
}
64+
if let Some(ref guard) = first_arm.guard() {
65+
arm_str += &format!(" {}", &guard.to_string());
66+
}
67+
if invert_matches {
68+
builder.replace(target_range, format!("!matches!({}, {})", expr, arm_str));
69+
} else {
70+
builder.replace(target_range, format!("matches!({}, {})", expr, arm_str));
71+
}
72+
},
73+
)
74+
}
75+
76+
fn is_bool_literal_expr(expr: &Option<ast::Expr>, expect_bool: bool) -> bool {
77+
if let Some(ast::Expr::Literal(lit)) = expr {
78+
if let ast::LiteralKind::Bool(b) = lit.kind() {
79+
return b == expect_bool;
80+
}
81+
}
82+
83+
return false;
84+
}
85+
86+
#[cfg(test)]
87+
mod tests {
88+
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
89+
90+
use super::convert_two_arm_bool_match_to_matches_macro;
91+
92+
#[test]
93+
fn not_applicable_outside_of_range_left() {
94+
check_assist_not_applicable(
95+
convert_two_arm_bool_match_to_matches_macro,
96+
r#"
97+
fn foo(a: Option<u32>) -> bool {
98+
$0 match a {
99+
Some(_val) => true,
100+
_ => false
101+
}
102+
}
103+
"#,
104+
);
105+
}
106+
107+
#[test]
108+
fn not_applicable_non_two_arm_match() {
109+
cov_mark::check!(non_two_arm_match);
110+
check_assist_not_applicable(
111+
convert_two_arm_bool_match_to_matches_macro,
112+
r#"
113+
fn foo(a: Option<u32>) -> bool {
114+
match a$0 {
115+
Some(3) => true,
116+
Some(4) => true,
117+
_ => false
118+
}
119+
}
120+
"#,
121+
);
122+
}
123+
124+
#[test]
125+
fn not_applicable_non_bool_literal_arms() {
126+
cov_mark::check!(non_invert_bool_literal_arms);
127+
check_assist_not_applicable(
128+
convert_two_arm_bool_match_to_matches_macro,
129+
r#"
130+
fn foo(a: Option<u32>) -> bool {
131+
match a$0 {
132+
Some(val) => val == 3,
133+
_ => false
134+
}
135+
}
136+
"#,
137+
);
138+
}
139+
#[test]
140+
fn not_applicable_both_false_arms() {
141+
cov_mark::check!(non_invert_bool_literal_arms);
142+
check_assist_not_applicable(
143+
convert_two_arm_bool_match_to_matches_macro,
144+
r#"
145+
fn foo(a: Option<u32>) -> bool {
146+
match a$0 {
147+
Some(val) => false,
148+
_ => false
149+
}
150+
}
151+
"#,
152+
);
153+
}
154+
155+
#[test]
156+
fn not_applicable_both_true_arms() {
157+
cov_mark::check!(non_invert_bool_literal_arms);
158+
check_assist_not_applicable(
159+
convert_two_arm_bool_match_to_matches_macro,
160+
r#"
161+
fn foo(a: Option<u32>) -> bool {
162+
match a$0 {
163+
Some(val) => true,
164+
_ => true
165+
}
166+
}
167+
"#,
168+
);
169+
}
170+
171+
#[test]
172+
fn convert_simple_case() {
173+
check_assist(
174+
convert_two_arm_bool_match_to_matches_macro,
175+
r#"
176+
fn foo(a: Option<u32>) -> bool {
177+
match a$0 {
178+
Some(_val) => true,
179+
_ => false
180+
}
181+
}
182+
"#,
183+
r#"
184+
fn foo(a: Option<u32>) -> bool {
185+
matches!(a, Some(_val))
186+
}
187+
"#,
188+
);
189+
}
190+
191+
#[test]
192+
fn convert_simple_invert_case() {
193+
check_assist(
194+
convert_two_arm_bool_match_to_matches_macro,
195+
r#"
196+
fn foo(a: Option<u32>) -> bool {
197+
match a$0 {
198+
Some(_val) => false,
199+
_ => true
200+
}
201+
}
202+
"#,
203+
r#"
204+
fn foo(a: Option<u32>) -> bool {
205+
!matches!(a, Some(_val))
206+
}
207+
"#,
208+
);
209+
}
210+
211+
#[test]
212+
fn convert_with_guard_case() {
213+
check_assist(
214+
convert_two_arm_bool_match_to_matches_macro,
215+
r#"
216+
fn foo(a: Option<u32>) -> bool {
217+
match a$0 {
218+
Some(val) if val > 3 => true,
219+
_ => false
220+
}
221+
}
222+
"#,
223+
r#"
224+
fn foo(a: Option<u32>) -> bool {
225+
matches!(a, Some(val) if val > 3)
226+
}
227+
"#,
228+
);
229+
}
230+
231+
#[test]
232+
fn convert_enum_match_cases() {
233+
check_assist(
234+
convert_two_arm_bool_match_to_matches_macro,
235+
r#"
236+
enum X { A, B }
237+
238+
fn foo(a: X) -> bool {
239+
match a$0 {
240+
X::A => true,
241+
_ => false
242+
}
243+
}
244+
"#,
245+
r#"
246+
enum X { A, B }
247+
248+
fn foo(a: X) -> bool {
249+
matches!(a, X::A)
250+
}
251+
"#,
252+
);
253+
}
254+
255+
#[test]
256+
fn convert_target_simple() {
257+
check_assist_target(
258+
convert_two_arm_bool_match_to_matches_macro,
259+
r#"
260+
fn foo(a: Option<u32>) -> bool {
261+
match a$0 {
262+
Some(val) => true,
263+
_ => false
264+
}
265+
}
266+
"#,
267+
r#"match a {
268+
Some(val) => true,
269+
_ => false
270+
}"#,
271+
);
272+
}
273+
274+
#[test]
275+
fn convert_target_complex() {
276+
check_assist_target(
277+
convert_two_arm_bool_match_to_matches_macro,
278+
r#"
279+
enum E { X, Y }
280+
281+
fn main() {
282+
match E::X$0 {
283+
E::X => true,
284+
_ => false,
285+
}
286+
}
287+
"#,
288+
"match E::X {
289+
E::X => true,
290+
_ => false,
291+
}",
292+
);
293+
}
294+
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ mod handlers {
122122
mod convert_let_else_to_match;
123123
mod convert_tuple_struct_to_named_struct;
124124
mod convert_to_guarded_return;
125+
mod convert_two_arm_bool_match_to_matches_macro;
125126
mod convert_while_to_loop;
126127
mod destructure_tuple_binding;
127128
mod expand_glob_import;
@@ -216,6 +217,7 @@ mod handlers {
216217
convert_let_else_to_match::convert_let_else_to_match,
217218
convert_to_guarded_return::convert_to_guarded_return,
218219
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
220+
convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro,
219221
convert_while_to_loop::convert_while_to_loop,
220222
destructure_tuple_binding::destructure_tuple_binding,
221223
expand_glob_import::expand_glob_import,

crates/ide-assists/src/tests/generated.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,26 @@ impl Point {
472472
)
473473
}
474474

475+
#[test]
476+
fn doctest_convert_two_arm_bool_match_to_matches_macro() {
477+
check_doc_test(
478+
"convert_two_arm_bool_match_to_matches_macro",
479+
r#####"
480+
fn main() {
481+
match scrutinee$0 {
482+
Some(val) if val.cond() => true,
483+
_ => false,
484+
}
485+
}
486+
"#####,
487+
r#####"
488+
fn main() {
489+
matches!(scrutinee, Some(val) if val.cond())
490+
}
491+
"#####,
492+
)
493+
}
494+
475495
#[test]
476496
fn doctest_convert_while_to_loop() {
477497
check_doc_test(

0 commit comments

Comments
 (0)