Skip to content

Commit 4661a60

Browse files
committed
Add convert_two_arm_bool_match_to_matches_macro ide-assists
1 parent d9e2207 commit 4661a60

File tree

3 files changed

+315
-0
lines changed

3 files changed

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

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)