Skip to content

Commit 9674490

Browse files
Merge #7981
7981: Allow applying De Morgan's law to multiple terms at once r=matklad a=shepmaster Co-authored-by: Jake Goulding <[email protected]>
2 parents ceffcf8 + 63155d6 commit 9674490

File tree

1 file changed

+76
-11
lines changed

1 file changed

+76
-11
lines changed

crates/ide_assists/src/handlers/apply_demorgan.rs

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::VecDeque;
2+
13
use syntax::ast::{self, AstNode};
24

35
use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
@@ -30,19 +32,52 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
3032
return None;
3133
}
3234

33-
let lhs = expr.lhs()?;
34-
let lhs_range = lhs.syntax().text_range();
35-
let not_lhs = invert_boolean_expression(&ctx.sema, lhs);
35+
let mut expr = expr;
36+
37+
// Walk up the tree while we have the same binary operator
38+
while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) {
39+
if let Some(parent_op) = expr.op_kind() {
40+
if parent_op == op {
41+
expr = parent_expr
42+
}
43+
}
44+
}
45+
46+
let mut expr_stack = vec![expr.clone()];
47+
let mut terms = Vec::new();
48+
let mut op_ranges = Vec::new();
49+
50+
// Find all the children with the same binary operator
51+
while let Some(expr) = expr_stack.pop() {
52+
let mut traverse_bin_expr_arm = |expr| {
53+
if let ast::Expr::BinExpr(bin_expr) = expr {
54+
if let Some(expr_op) = bin_expr.op_kind() {
55+
if expr_op == op {
56+
expr_stack.push(bin_expr);
57+
} else {
58+
terms.push(ast::Expr::BinExpr(bin_expr));
59+
}
60+
} else {
61+
terms.push(ast::Expr::BinExpr(bin_expr));
62+
}
63+
} else {
64+
terms.push(expr);
65+
}
66+
};
3667

37-
let rhs = expr.rhs()?;
38-
let rhs_range = rhs.syntax().text_range();
39-
let not_rhs = invert_boolean_expression(&ctx.sema, rhs);
68+
op_ranges.extend(expr.op_token().map(|t| t.text_range()));
69+
traverse_bin_expr_arm(expr.lhs()?);
70+
traverse_bin_expr_arm(expr.rhs()?);
71+
}
4072

4173
acc.add(
4274
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
4375
"Apply De Morgan's law",
4476
op_range,
4577
|edit| {
78+
terms.sort_by_key(|t| t.syntax().text_range().start());
79+
let mut terms = VecDeque::from(terms);
80+
4681
let paren_expr = expr.syntax().parent().and_then(|parent| ast::ParenExpr::cast(parent));
4782

4883
let neg_expr = paren_expr
@@ -57,11 +92,18 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
5792
}
5893
});
5994

60-
edit.replace(op_range, opposite_op);
95+
for op_range in op_ranges {
96+
edit.replace(op_range, opposite_op);
97+
}
6198

6299
if let Some(paren_expr) = paren_expr {
63-
edit.replace(lhs_range, not_lhs.syntax().text());
64-
edit.replace(rhs_range, not_rhs.syntax().text());
100+
for term in terms {
101+
let range = term.syntax().text_range();
102+
let not_term = invert_boolean_expression(&ctx.sema, term);
103+
104+
edit.replace(range, not_term.syntax().text());
105+
}
106+
65107
if let Some(neg_expr) = neg_expr {
66108
cov_mark::hit!(demorgan_double_negation);
67109
edit.replace(neg_expr.op_token().unwrap().text_range(), "");
@@ -70,8 +112,25 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
70112
edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!(");
71113
}
72114
} else {
73-
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
74-
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
115+
if let Some(lhs) = terms.pop_front() {
116+
let lhs_range = lhs.syntax().text_range();
117+
let not_lhs = invert_boolean_expression(&ctx.sema, lhs);
118+
119+
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
120+
}
121+
122+
if let Some(rhs) = terms.pop_back() {
123+
let rhs_range = rhs.syntax().text_range();
124+
let not_rhs = invert_boolean_expression(&ctx.sema, rhs);
125+
126+
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
127+
}
128+
129+
for term in terms {
130+
let term_range = term.syntax().text_range();
131+
let not_term = invert_boolean_expression(&ctx.sema, term);
132+
edit.replace(term_range, not_term.syntax().text());
133+
}
75134
}
76135
},
77136
)
@@ -179,6 +238,12 @@ fn f() {
179238
check_assist(apply_demorgan, "fn f() { x ||$0 x }", "fn f() { !(!x && !x) }")
180239
}
181240

241+
#[test]
242+
fn demorgan_multiple_terms() {
243+
check_assist(apply_demorgan, "fn f() { x ||$0 y || z }", "fn f() { !(!x && !y && !z) }");
244+
check_assist(apply_demorgan, "fn f() { x || y ||$0 z }", "fn f() { !(!x && !y && !z) }");
245+
}
246+
182247
#[test]
183248
fn demorgan_doesnt_apply_with_cursor_not_on_op() {
184249
check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }")

0 commit comments

Comments
 (0)