Skip to content

Commit 14fa82b

Browse files
committed
fix: Enhance remove_parentheses assist to handle return expressions
1 parent 8b45966 commit 14fa82b

File tree

2 files changed

+97
-20
lines changed

2 files changed

+97
-20
lines changed

crates/ide-assists/src/handlers/remove_parentheses.rs

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,18 @@ mod tests {
163163
}
164164

165165
#[test]
166-
fn remove_parens_prefix_with_return_no_value() {
167-
// removing `()` from !(return) would make `!return` which is invalid syntax
168-
check_assist_not_applicable(
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(
169172
remove_parentheses,
170-
r#"fn main() { let _x = true && !$0(return) || true; }"#,
173+
r#"fn f() { !$0(return false) }"#,
174+
r#"fn f() { !return false }"#,
171175
);
172-
check_assist_not_applicable(remove_parentheses, r#"fn f() { !$0(return) }"#);
173-
check_assist_not_applicable(remove_parentheses, r#"fn f() { !$0(break) }"#);
174-
check_assist_not_applicable(remove_parentheses, r#"fn f() { !$0(continue) }"#);
175176

176-
// Binary operators should still allow removal
177+
// Binary operators should still allow removal unless a ret-like expression is immediately followed by `||` or `&&`.
177178
check_assist(
178179
remove_parentheses,
179180
r#"fn f() { true || $0(return) }"#,
@@ -247,6 +248,79 @@ mod tests {
247248
);
248249
}
249250

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+
250324
#[test]
251325
fn remove_parens_double_paren_stmt() {
252326
check_assist(

crates/syntax/src/ast/prec.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
use stdx::always;
44

55
use crate::{
6-
AstNode, SyntaxNode,
6+
AstNode, Direction, SyntaxNode, T,
7+
algo::skip_trivia_token,
78
ast::{self, BinaryOp, Expr, HasArgList, RangeItem},
89
match_ast,
910
};
1011

1112
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
1213
pub enum ExprPrecedence {
13-
// return val, break val, yield val, closures
14+
// return, break, continue, yield, yeet, become (with or without value)
1415
Jump,
1516
// = += -= *= /= %= &= |= ^= <<= >>=
1617
Assign,
@@ -76,18 +77,12 @@ pub fn precedence(expr: &ast::Expr) -> ExprPrecedence {
7677
Some(_) => ExprPrecedence::Unambiguous,
7778
},
7879

79-
Expr::BreakExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
80-
Expr::BecomeExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
81-
Expr::ReturnExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
82-
Expr::YeetExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
83-
Expr::YieldExpr(e) if e.expr().is_some() => ExprPrecedence::Jump,
84-
8580
Expr::BreakExpr(_)
8681
| Expr::BecomeExpr(_)
8782
| Expr::ReturnExpr(_)
8883
| Expr::YeetExpr(_)
8984
| Expr::YieldExpr(_)
90-
| Expr::ContinueExpr(_) => ExprPrecedence::Unambiguous,
85+
| Expr::ContinueExpr(_) => ExprPrecedence::Jump,
9186

9287
Expr::RangeExpr(..) => ExprPrecedence::Range,
9388

@@ -226,9 +221,17 @@ impl Expr {
226221
return false;
227222
}
228223

229-
// Special-case prefix operators with return/break/etc without value
230-
// e.g., `!(return)` - parentheses are necessary
231-
if self.is_ret_like_with_no_value() && parent.is_prefix() {
224+
// Keep parens when a ret-like expr is followed by `||` or `&&`.
225+
// For `||`, removing parens could reparse as `<ret-like> || <closure>`.
226+
// For `&&`, we avoid introducing `<ret-like> && <expr>` into a binary chain.
227+
228+
if self.precedence() == ExprPrecedence::Jump
229+
&& let Some(node) =
230+
place_of.ancestors().find(|it| it.parent().is_none_or(|p| &p == parent.syntax()))
231+
&& let Some(next) =
232+
node.last_token().and_then(|t| skip_trivia_token(t.next_token()?, Direction::Next))
233+
&& matches!(next.kind(), T![||] | T![&&])
234+
{
232235
return true;
233236
}
234237

0 commit comments

Comments
 (0)