ide_assists/handlers/
remove_parentheses.rs

1use syntax::{
2    AstNode, SyntaxKind, T,
3    ast::{self, syntax_factory::SyntaxFactory},
4    syntax_editor::Position,
5};
6
7use crate::{AssistContext, AssistId, Assists};
8
9// Assist: remove_parentheses
10//
11// Removes redundant parentheses.
12//
13// ```
14// fn main() {
15//     _ = $0(2) + 2;
16// }
17// ```
18// ->
19// ```
20// fn main() {
21//     _ = 2 + 2;
22// }
23// ```
24pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
25    let parens = ctx.find_node_at_offset::<ast::ParenExpr>()?;
26
27    let cursor_in_range =
28        parens.l_paren_token()?.text_range().contains_range(ctx.selection_trimmed())
29            || parens.r_paren_token()?.text_range().contains_range(ctx.selection_trimmed());
30    if !cursor_in_range {
31        return None;
32    }
33
34    let expr = parens.expr()?;
35
36    let parent = parens.syntax().parent()?;
37    if expr.needs_parens_in(&parent) {
38        return None;
39    }
40
41    let target = parens.syntax().text_range();
42    acc.add(
43        AssistId::refactor("remove_parentheses"),
44        "Remove redundant parentheses",
45        target,
46        |builder| {
47            let mut editor = builder.make_editor(parens.syntax());
48            let prev_token = parens.syntax().first_token().and_then(|it| it.prev_token());
49            let need_to_add_ws = match prev_token {
50                Some(it) => {
51                    let tokens = [T![&], T![!], T!['('], T!['['], T!['{']];
52                    it.kind() != SyntaxKind::WHITESPACE && !tokens.contains(&it.kind())
53                }
54                None => false,
55            };
56            if need_to_add_ws {
57                let make = SyntaxFactory::with_mappings();
58                editor.insert(Position::before(parens.syntax()), make.whitespace(" "));
59                editor.add_mappings(make.finish_with_mappings());
60            }
61            editor.replace(parens.syntax(), expr.syntax());
62            builder.add_file_edits(ctx.vfs_file_id(), editor);
63        },
64    )
65}
66
67#[cfg(test)]
68mod tests {
69    use crate::tests::{check_assist, check_assist_not_applicable};
70
71    use super::*;
72
73    #[test]
74    fn remove_parens_space() {
75        check_assist(
76            remove_parentheses,
77            r#"fn f() { match$0(true) {} }"#,
78            r#"fn f() { match true {} }"#,
79        );
80    }
81
82    #[test]
83    fn remove_parens_simple() {
84        check_assist(remove_parentheses, r#"fn f() { $0(2) + 2; }"#, r#"fn f() { 2 + 2; }"#);
85        check_assist(remove_parentheses, r#"fn f() { ($02) + 2; }"#, r#"fn f() { 2 + 2; }"#);
86        check_assist(remove_parentheses, r#"fn f() { (2)$0 + 2; }"#, r#"fn f() { 2 + 2; }"#);
87        check_assist(remove_parentheses, r#"fn f() { (2$0) + 2; }"#, r#"fn f() { 2 + 2; }"#);
88    }
89
90    #[test]
91    fn remove_parens_closure() {
92        check_assist(remove_parentheses, r#"fn f() { &$0(|| 42) }"#, r#"fn f() { &|| 42 }"#);
93
94        check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(|| 42).f() }"#);
95    }
96
97    #[test]
98    fn remove_parens_if_let_chains() {
99        check_assist_not_applicable(
100            remove_parentheses,
101            r#"fn f() { if let true = $0(true && true) {} }"#,
102        );
103    }
104
105    #[test]
106    fn remove_parens_associativity() {
107        check_assist(
108            remove_parentheses,
109            r#"fn f() { $0(2 + 2) + 2; }"#,
110            r#"fn f() { 2 + 2 + 2; }"#,
111        );
112        check_assist_not_applicable(remove_parentheses, r#"fn f() { 2 + $0(2 + 2); }"#);
113    }
114
115    #[test]
116    fn remove_parens_precedence() {
117        check_assist(
118            remove_parentheses,
119            r#"fn f() { $0(2 * 3) + 1; }"#,
120            r#"fn f() { 2 * 3 + 1; }"#,
121        );
122        check_assist(remove_parentheses, r#"fn f() { ( $0(2) ); }"#, r#"fn f() { ( 2 ); }"#);
123        check_assist(remove_parentheses, r#"fn f() { $0(2?)?; }"#, r#"fn f() { 2??; }"#);
124        check_assist(remove_parentheses, r#"fn f() { f(($02 + 2)); }"#, r#"fn f() { f(2 + 2); }"#);
125        check_assist(
126            remove_parentheses,
127            r#"fn f() { (1<2) &&$0(3>4); }"#,
128            r#"fn f() { (1<2) && 3>4; }"#,
129        );
130    }
131
132    #[test]
133    fn remove_parens_doesnt_apply_precedence() {
134        check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2) * 8; }"#);
135        check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2).f(); }"#);
136        check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2).await; }"#);
137        check_assist_not_applicable(remove_parentheses, r#"fn f() { $0!(2..2); }"#);
138    }
139
140    #[test]
141    fn remove_parens_doesnt_apply_with_cursor_not_on_paren() {
142        check_assist_not_applicable(remove_parentheses, r#"fn f() { (2 +$0 2) }"#);
143        check_assist_not_applicable(remove_parentheses, r#"fn f() {$0 (2 + 2) }"#);
144    }
145
146    #[test]
147    fn remove_parens_doesnt_apply_when_expr_would_be_turned_into_a_statement() {
148        check_assist_not_applicable(remove_parentheses, r#"fn x() -> u8 { $0({ 0 } + 1) }"#);
149        check_assist_not_applicable(
150            remove_parentheses,
151            r#"fn x() -> u8 { $0(if true { 0 } else { 1 } + 1) }"#,
152        );
153        check_assist_not_applicable(remove_parentheses, r#"fn x() -> u8 { $0(loop {} + 1) }"#);
154    }
155
156    #[test]
157    fn remove_parens_doesnt_apply_weird_syntax_and_edge_cases() {
158        // removing `()` would break code because {} would be counted as the loop/if body
159        check_assist_not_applicable(remove_parentheses, r#"fn f() { for _ in $0(0..{3}) {} }"#);
160        check_assist_not_applicable(remove_parentheses, r#"fn f() { for _ in $0(S {}) {} }"#);
161        check_assist_not_applicable(remove_parentheses, r#"fn f() { if $0(S {} == 2) {} }"#);
162        check_assist_not_applicable(remove_parentheses, r#"fn f() { if $0(return) {} }"#);
163    }
164
165    #[test]
166    fn remove_parens_prefix_with_ret_like_prefix() {
167        check_assist(remove_parentheses, r#"fn f() { !$0(return) }"#, r#"fn f() { !return }"#);
168        // `break`, `continue` behave the same under prefix operators
169        check_assist(remove_parentheses, r#"fn f() { !$0(break) }"#, r#"fn f() { !break }"#);
170        check_assist(remove_parentheses, r#"fn f() { !$0(continue) }"#, r#"fn f() { !continue }"#);
171        check_assist(
172            remove_parentheses,
173            r#"fn f() { !$0(return false) }"#,
174            r#"fn f() { !return false }"#,
175        );
176
177        // Binary operators should still allow removal unless a ret-like expression is immediately followed by `||` or `&&`.
178        check_assist(
179            remove_parentheses,
180            r#"fn f() { true || $0(return) }"#,
181            r#"fn f() { true || return }"#,
182        );
183        check_assist(
184            remove_parentheses,
185            r#"fn f() { cond && $0(return) }"#,
186            r#"fn f() { cond && return }"#,
187        );
188    }
189
190    #[test]
191    fn remove_parens_return_with_value_followed_by_block() {
192        check_assist(
193            remove_parentheses,
194            r#"fn f() { if $0(return ()) {} }"#,
195            r#"fn f() { if return () {} }"#,
196        );
197    }
198
199    #[test]
200    fn remove_exprs_let_else_restrictions() {
201        // `}` is not allowed before `else` here
202        check_assist_not_applicable(
203            remove_parentheses,
204            r#"fn f() { let _ = $0(S{}) else { return }; }"#,
205        );
206
207        // logic operators can't directly appear in the let-else
208        check_assist_not_applicable(
209            remove_parentheses,
210            r#"fn f() { let _ = $0(false || false) else { return }; }"#,
211        );
212        check_assist_not_applicable(
213            remove_parentheses,
214            r#"fn f() { let _ = $0(true && true) else { return }; }"#,
215        );
216    }
217
218    #[test]
219    fn remove_parens_weird_places() {
220        check_assist(
221            remove_parentheses,
222            r#"fn f() { match () { _ =>$0(()) } }"#,
223            r#"fn f() { match () { _ => () } }"#,
224        );
225
226        check_assist(
227            remove_parentheses,
228            r#"fn x() -> u8 { { [$0({ 0 } + 1)] } }"#,
229            r#"fn x() -> u8 { { [{ 0 } + 1] } }"#,
230        );
231    }
232
233    #[test]
234    fn remove_parens_return_dot_f() {
235        check_assist(
236            remove_parentheses,
237            r#"fn f() { $0(return).f() }"#,
238            r#"fn f() { return.f() }"#,
239        );
240    }
241
242    #[test]
243    fn remove_parens_prefix_then_return_something() {
244        check_assist(
245            remove_parentheses,
246            r#"fn f() { &$0(return ()) }"#,
247            r#"fn f() { &return () }"#,
248        );
249    }
250
251    #[test]
252    fn remove_parens_return_in_unary_not() {
253        check_assist(
254            remove_parentheses,
255            r#"fn f() { cond && !$0(return) }"#,
256            r#"fn f() { cond && !return }"#,
257        );
258        check_assist(
259            remove_parentheses,
260            r#"fn f() { cond && !$0(return false) }"#,
261            r#"fn f() { cond && !return false }"#,
262        );
263    }
264
265    #[test]
266    fn remove_parens_return_in_disjunction_with_closure_risk() {
267        // `return` may only be blocked when it would form `return ||` or `return &&`
268        check_assist_not_applicable(
269            remove_parentheses,
270            r#"fn f() { let _x = true && $0(return) || true; }"#,
271        );
272        check_assist_not_applicable(
273            remove_parentheses,
274            r#"fn f() { let _x = true && !$0(return) || true; }"#,
275        );
276        check_assist_not_applicable(
277            remove_parentheses,
278            r#"fn f() { let _x = true && $0(return false) || true; }"#,
279        );
280        check_assist_not_applicable(
281            remove_parentheses,
282            r#"fn f() { let _x = true && !$0(return false) || true; }"#,
283        );
284        check_assist_not_applicable(
285            remove_parentheses,
286            r#"fn f() { let _x = true && $0(return) && true; }"#,
287        );
288        check_assist_not_applicable(
289            remove_parentheses,
290            r#"fn f() { let _x = true && !$0(return) && true; }"#,
291        );
292        check_assist_not_applicable(
293            remove_parentheses,
294            r#"fn f() { let _x = true && $0(return false) && true; }"#,
295        );
296        check_assist_not_applicable(
297            remove_parentheses,
298            r#"fn f() { let _x = true && !$0(return false) && true; }"#,
299        );
300        check_assist_not_applicable(
301            remove_parentheses,
302            r#"fn f() { let _x = $0(return) || true; }"#,
303        );
304        check_assist_not_applicable(
305            remove_parentheses,
306            r#"fn f() { let _x = $0(return) && true; }"#,
307        );
308    }
309
310    #[test]
311    fn remove_parens_return_in_disjunction_is_ok() {
312        check_assist(
313            remove_parentheses,
314            r#"fn f() { let _x = true || $0(return); }"#,
315            r#"fn f() { let _x = true || return; }"#,
316        );
317        check_assist(
318            remove_parentheses,
319            r#"fn f() { let _x = true && $0(return); }"#,
320            r#"fn f() { let _x = true && return; }"#,
321        );
322    }
323
324    #[test]
325    fn remove_parens_double_paren_stmt() {
326        check_assist(
327            remove_parentheses,
328            r#"fn x() -> u8 { $0(({ 0 } + 1)) }"#,
329            r#"fn x() -> u8 { ({ 0 } + 1) }"#,
330        );
331
332        check_assist(
333            remove_parentheses,
334            r#"fn x() -> u8 { (($0{ 0 } + 1)) }"#,
335            r#"fn x() -> u8 { ({ 0 } + 1) }"#,
336        );
337    }
338
339    #[test]
340    fn remove_parens_im_tired_of_naming_tests() {
341        check_assist(
342            remove_parentheses,
343            r#"fn f() { 2 + $0(return 2) }"#,
344            r#"fn f() { 2 + return 2 }"#,
345        );
346
347        check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(return 2) + 2 }"#);
348    }
349
350    #[test]
351    fn remove_parens_indirect_calls() {
352        check_assist(
353            remove_parentheses,
354            r#"fn f(call: fn(usize), arg: usize) { $0(call)(arg); }"#,
355            r#"fn f(call: fn(usize), arg: usize) { call(arg); }"#,
356        );
357        check_assist(
358            remove_parentheses,
359            r#"fn f<F>(call: F, arg: usize) where F: Fn(usize) { $0(call)(arg); }"#,
360            r#"fn f<F>(call: F, arg: usize) where F: Fn(usize) { call(arg); }"#,
361        );
362
363        // Parentheses are necessary when calling a function-like pointer that is a member of a struct or union.
364        check_assist_not_applicable(
365            remove_parentheses,
366            r#"
367struct Foo<T> {
368    t: T,
369}
370
371impl Foo<fn(usize)> {
372    fn foo(&self, arg: usize) {
373        $0(self.t)(arg);
374    }
375}"#,
376        );
377    }
378}