]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs
Auto merge of #103913 - Neutron3529:patch-1, r=thomcc
[rust.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / convert_two_arm_bool_match_to_matches_macro.rs
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(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
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 }