Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit ef5c6da

Browse files
committed
Rewrite DeMorgan without str manipulation.
1 parent ed4e28b commit ef5c6da

File tree

2 files changed

+117
-95
lines changed

2 files changed

+117
-95
lines changed
Lines changed: 116 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use std::collections::VecDeque;
22

3-
use syntax::ast::{self, AstNode};
3+
use syntax::{
4+
ast::{self, AstNode, Expr::BinExpr},
5+
ted::{self, Position},
6+
SyntaxKind,
7+
};
48

59
use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
610

@@ -23,131 +27,124 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin
2327
// }
2428
// ```
2529
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
26-
let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
27-
let op = expr.op_kind()?;
28-
let op_range = expr.op_token()?.text_range();
30+
let mut bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
31+
let op = bin_expr.op_kind()?;
32+
let op_range = bin_expr.op_token()?.text_range();
2933

30-
let opposite_op = match op {
31-
ast::BinaryOp::LogicOp(ast::LogicOp::And) => "||",
32-
ast::BinaryOp::LogicOp(ast::LogicOp::Or) => "&&",
33-
_ => return None,
34-
};
35-
36-
let cursor_in_range = op_range.contains_range(ctx.selection_trimmed());
37-
if !cursor_in_range {
34+
// Is the cursor on the expression's logical operator?
35+
if !op_range.contains_range(ctx.selection_trimmed()) {
3836
return None;
3937
}
4038

41-
let mut expr = expr;
42-
4339
// Walk up the tree while we have the same binary operator
44-
while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) {
45-
match expr.op_kind() {
40+
while let Some(parent_expr) = bin_expr.syntax().parent().and_then(ast::BinExpr::cast) {
41+
match parent_expr.op_kind() {
4642
Some(parent_op) if parent_op == op => {
47-
expr = parent_expr;
43+
bin_expr = parent_expr;
4844
}
4945
_ => break,
5046
}
5147
}
5248

53-
let mut expr_stack = vec![expr.clone()];
54-
let mut terms = Vec::new();
55-
let mut op_ranges = Vec::new();
56-
57-
// Find all the children with the same binary operator
58-
while let Some(expr) = expr_stack.pop() {
59-
let mut traverse_bin_expr_arm = |expr| {
60-
if let ast::Expr::BinExpr(bin_expr) = expr {
61-
if let Some(expr_op) = bin_expr.op_kind() {
62-
if expr_op == op {
63-
expr_stack.push(bin_expr);
64-
} else {
65-
terms.push(ast::Expr::BinExpr(bin_expr));
66-
}
49+
let op = bin_expr.op_kind()?;
50+
let inv_token = match op {
51+
ast::BinaryOp::LogicOp(ast::LogicOp::And) => SyntaxKind::PIPE2,
52+
ast::BinaryOp::LogicOp(ast::LogicOp::Or) => SyntaxKind::AMP2,
53+
_ => return None,
54+
};
55+
56+
let demorganed = bin_expr.clone_subtree().clone_for_update();
57+
58+
ted::replace(demorganed.op_token()?, ast::make::token(inv_token));
59+
let mut exprs = VecDeque::from(vec![
60+
(bin_expr.lhs()?, demorganed.lhs()?),
61+
(bin_expr.rhs()?, demorganed.rhs()?),
62+
]);
63+
64+
while let Some((expr, dm)) = exprs.pop_front() {
65+
if let BinExpr(bin_expr) = &expr {
66+
if let BinExpr(cbin_expr) = &dm {
67+
if op == bin_expr.op_kind()? {
68+
ted::replace(cbin_expr.op_token()?, ast::make::token(inv_token));
69+
exprs.push_back((bin_expr.lhs()?, cbin_expr.lhs()?));
70+
exprs.push_back((bin_expr.rhs()?, cbin_expr.rhs()?));
6771
} else {
68-
terms.push(ast::Expr::BinExpr(bin_expr));
72+
let mut inv = invert_boolean_expression(expr);
73+
if inv.needs_parens_in(dm.syntax().parent()?) {
74+
inv = ast::make::expr_paren(inv).clone_for_update();
75+
}
76+
ted::replace(dm.syntax(), inv.syntax());
6977
}
7078
} else {
71-
terms.push(expr);
79+
return None;
7280
}
73-
};
74-
75-
op_ranges.extend(expr.op_token().map(|t| t.text_range()));
76-
traverse_bin_expr_arm(expr.lhs()?);
77-
traverse_bin_expr_arm(expr.rhs()?);
81+
} else {
82+
let mut inv = invert_boolean_expression(dm.clone_subtree()).clone_for_update();
83+
if inv.needs_parens_in(dm.syntax().parent()?) {
84+
inv = ast::make::expr_paren(inv).clone_for_update();
85+
}
86+
ted::replace(dm.syntax(), inv.syntax());
87+
}
7888
}
7989

90+
let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast);
91+
let neg_expr = paren_expr
92+
.clone()
93+
.and_then(|paren_expr| paren_expr.syntax().parent())
94+
.and_then(ast::PrefixExpr::cast)
95+
.and_then(|prefix_expr| {
96+
if prefix_expr.op_kind().unwrap() == ast::UnaryOp::Not {
97+
Some(prefix_expr)
98+
} else {
99+
None
100+
}
101+
});
102+
80103
acc.add(
81104
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
82105
"Apply De Morgan's law",
83106
op_range,
84107
|edit| {
85-
terms.sort_by_key(|t| t.syntax().text_range().start());
86-
let mut terms = VecDeque::from(terms);
87-
88-
let paren_expr = expr.syntax().parent().and_then(ast::ParenExpr::cast);
89-
90-
let neg_expr = paren_expr
91-
.clone()
92-
.and_then(|paren_expr| paren_expr.syntax().parent())
93-
.and_then(ast::PrefixExpr::cast)
94-
.and_then(|prefix_expr| {
95-
if prefix_expr.op_kind().unwrap() == ast::UnaryOp::Not {
96-
Some(prefix_expr)
97-
} else {
98-
None
99-
}
100-
});
101-
102-
for op_range in op_ranges {
103-
edit.replace(op_range, opposite_op);
104-
}
105-
106108
if let Some(paren_expr) = paren_expr {
107-
for term in terms {
108-
let range = term.syntax().text_range();
109-
let not_term = invert_boolean_expression(term);
110-
111-
edit.replace(range, not_term.syntax().text());
112-
}
113-
114109
if let Some(neg_expr) = neg_expr {
115110
cov_mark::hit!(demorgan_double_negation);
116-
edit.replace(neg_expr.op_token().unwrap().text_range(), "");
111+
edit.replace_ast(ast::Expr::PrefixExpr(neg_expr), demorganed.into());
117112
} else {
118113
cov_mark::hit!(demorgan_double_parens);
119-
edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!(");
114+
ted::insert_all_raw(
115+
Position::before(demorganed.lhs().unwrap().syntax()),
116+
vec![
117+
syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::BANG)),
118+
syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::L_PAREN)),
119+
],
120+
);
121+
122+
ted::append_child_raw(
123+
demorganed.syntax(),
124+
syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::R_PAREN)),
125+
);
126+
127+
edit.replace_ast(ast::Expr::ParenExpr(paren_expr), demorganed.into());
120128
}
121129
} else {
122-
if let Some(lhs) = terms.pop_front() {
123-
let lhs_range = lhs.syntax().text_range();
124-
let not_lhs = invert_boolean_expression(lhs);
125-
126-
edit.replace(lhs_range, format!("!({not_lhs}"));
127-
}
128-
129-
if let Some(rhs) = terms.pop_back() {
130-
let rhs_range = rhs.syntax().text_range();
131-
let not_rhs = invert_boolean_expression(rhs);
132-
133-
edit.replace(rhs_range, format!("{not_rhs})"));
134-
}
135-
136-
for term in terms {
137-
let term_range = term.syntax().text_range();
138-
let not_term = invert_boolean_expression(term);
139-
edit.replace(term_range, not_term.to_string());
140-
}
130+
ted::insert_all_raw(
131+
Position::before(demorganed.lhs().unwrap().syntax()),
132+
vec![
133+
syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::BANG)),
134+
syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::L_PAREN)),
135+
],
136+
);
137+
ted::append_child_raw(demorganed.syntax(), ast::make::token(SyntaxKind::R_PAREN));
138+
edit.replace_ast(bin_expr, demorganed);
141139
}
142140
},
143141
)
144142
}
145143

146144
#[cfg(test)]
147145
mod tests {
148-
use crate::tests::{check_assist, check_assist_not_applicable};
149-
150146
use super::*;
147+
use crate::tests::{check_assist, check_assist_not_applicable};
151148

152149
#[test]
153150
fn demorgan_handles_leq() {
@@ -213,7 +210,7 @@ fn f() { !(S <= S || S < S) }
213210
#[test]
214211
fn demorgan_doesnt_double_negation() {
215212
cov_mark::check!(demorgan_double_negation);
216-
check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { (!x && !x) }")
213+
check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { !x && !x }")
217214
}
218215

219216
#[test]
@@ -222,13 +219,38 @@ fn f() { !(S <= S || S < S) }
222219
check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }")
223220
}
224221

225-
// https://github.com/rust-lang/rust-analyzer/issues/10963
222+
// FIXME : This needs to go.
223+
// // https://github.com/rust-lang/rust-analyzer/issues/10963
224+
// #[test]
225+
// fn demorgan_doesnt_hang() {
226+
// check_assist(
227+
// apply_demorgan,
228+
// "fn f() { 1 || 3 &&$0 4 || 5 }",
229+
// "fn f() { !(!1 || !3 || !4) || 5 }",
230+
// )
231+
// }
232+
233+
#[test]
234+
fn demorgan_keep_pars_for_op_precedence() {
235+
check_assist(
236+
apply_demorgan,
237+
"fn main() {
238+
let _ = !(!a ||$0 !(b || c));
239+
}
240+
",
241+
"fn main() {
242+
let _ = a && (b || c);
243+
}
244+
",
245+
);
246+
}
247+
226248
#[test]
227-
fn demorgan_doesnt_hang() {
249+
fn demorgan_removes_pars_in_eq_precedence() {
228250
check_assist(
229251
apply_demorgan,
230-
"fn f() { 1 || 3 &&$0 4 || 5 }",
231-
"fn f() { !(!1 || !3 || !4) || 5 }",
252+
"fn() { let x = a && !(!b |$0| !c); }",
253+
"fn() { let x = a && b && c; }",
232254
)
233255
}
234256
}

crates/syntax/src/ast/make.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@ pub mod tokens {
11001100

11011101
pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
11021102
SourceFile::parse(
1103-
"const C: <()>::Item = (1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p)\n;\n\n",
1103+
"const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p)\n;\n\n",
11041104
)
11051105
});
11061106

0 commit comments

Comments
 (0)