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 // ```
convert_two_arm_bool_match_to_matches_macro( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()>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(pat) = &first_arm.pat() {
62 arm_str += &pat.to_string();
63 }
64 if let Some(guard) = &first_arm.guard() {
65 arm_str += &format!(" {guard}");
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
is_bool_literal_expr(expr: &Option<ast::Expr>, expect_bool: bool) -> bool76 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]
not_applicable_outside_of_range_left()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]
not_applicable_non_two_arm_match()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]
not_applicable_non_bool_literal_arms()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]
not_applicable_both_false_arms()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]
not_applicable_both_true_arms()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]
convert_simple_case()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]
convert_simple_invert_case()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]
convert_with_guard_case()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]
convert_enum_match_cases()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]
convert_target_simple()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]
convert_target_complex()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 }
295