Skip to content

Commit bf0c394

Browse files
committed
Migrate apply_demorgan to SyntaxEditor
1 parent f06f1b8 commit bf0c394

File tree

6 files changed

+162
-52
lines changed

6 files changed

+162
-52
lines changed

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

+80-40
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ use std::collections::VecDeque;
33
use ide_db::{
44
assists::GroupLabel,
55
famous_defs::FamousDefs,
6-
source_change::SourceChangeBuilder,
76
syntax_helpers::node_ext::{for_each_tail_expr, walk_expr},
87
};
98
use syntax::{
10-
ast::{self, make, AstNode, Expr::BinExpr, HasArgList},
11-
ted, SyntaxKind, T,
9+
ast::{self, syntax_factory::SyntaxFactory, AstNode, Expr::BinExpr, HasArgList},
10+
syntax_editor::{Position, SyntaxEditor},
11+
SyntaxKind, SyntaxNode, T,
1212
};
1313

1414
use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
@@ -58,9 +58,12 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
5858
_ => return None,
5959
};
6060

61-
let demorganed = bin_expr.clone_subtree().clone_for_update();
61+
let make = SyntaxFactory::new();
62+
63+
let demorganed = bin_expr.clone_subtree();
64+
let mut editor = SyntaxEditor::new(demorganed.syntax().clone());
65+
editor.replace(demorganed.op_token()?, make.token(inv_token));
6266

63-
ted::replace(demorganed.op_token()?, ast::make::token(inv_token));
6467
let mut exprs = VecDeque::from([
6568
(bin_expr.lhs()?, demorganed.lhs()?),
6669
(bin_expr.rhs()?, demorganed.rhs()?),
@@ -70,35 +73,39 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
7073
if let BinExpr(bin_expr) = &expr {
7174
if let BinExpr(cbin_expr) = &dm {
7275
if op == bin_expr.op_kind()? {
73-
ted::replace(cbin_expr.op_token()?, ast::make::token(inv_token));
76+
editor.replace(cbin_expr.op_token()?, make.token(inv_token));
7477
exprs.push_back((bin_expr.lhs()?, cbin_expr.lhs()?));
7578
exprs.push_back((bin_expr.rhs()?, cbin_expr.rhs()?));
7679
} else {
77-
let mut inv = invert_boolean_expression(expr);
78-
if inv.needs_parens_in(dm.syntax().parent()?) {
79-
inv = ast::make::expr_paren(inv).clone_for_update();
80+
let mut inv = invert_boolean_expression(&make, expr);
81+
if needs_parens_in_place_of(&inv, &dm.syntax().parent()?, &dm) {
82+
inv = make.expr_paren(inv).into();
8083
}
81-
ted::replace(dm.syntax(), inv.syntax());
84+
editor.replace(dm.syntax(), inv.syntax());
8285
}
8386
} else {
8487
return None;
8588
}
8689
} else {
87-
let mut inv = invert_boolean_expression(dm.clone_subtree()).clone_for_update();
88-
if inv.needs_parens_in(dm.syntax().parent()?) {
89-
inv = ast::make::expr_paren(inv).clone_for_update();
90+
let mut inv = invert_boolean_expression(&make, dm.clone());
91+
if needs_parens_in_place_of(&inv, &dm.syntax().parent()?, &dm) {
92+
inv = make.expr_paren(inv).into();
9093
}
91-
ted::replace(dm.syntax(), inv.syntax());
94+
editor.replace(dm.syntax(), inv.syntax());
9295
}
9396
}
9497

98+
editor.add_mappings(make.finish_with_mappings());
99+
let edit = editor.finish();
100+
let demorganed = ast::Expr::cast(edit.new_root().clone())?;
101+
95102
acc.add_group(
96103
&GroupLabel("Apply De Morgan's law".to_owned()),
97104
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
98105
"Apply De Morgan's law",
99106
op_range,
100-
|edit| {
101-
let demorganed = ast::Expr::BinExpr(demorganed);
107+
|builder| {
108+
let make = SyntaxFactory::new();
102109
let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast);
103110
let neg_expr = paren_expr
104111
.clone()
@@ -107,24 +114,32 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
107114
.filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not)))
108115
.map(ast::Expr::PrefixExpr);
109116

117+
let mut editor;
110118
if let Some(paren_expr) = paren_expr {
111119
if let Some(neg_expr) = neg_expr {
112120
cov_mark::hit!(demorgan_double_negation);
113121
let parent = neg_expr.syntax().parent();
122+
editor = builder.make_editor(neg_expr.syntax());
114123

115124
if parent.is_some_and(|parent| demorganed.needs_parens_in(parent)) {
116125
cov_mark::hit!(demorgan_keep_parens_for_op_precedence2);
117-
edit.replace_ast(neg_expr, make::expr_paren(demorganed));
126+
editor.replace(neg_expr.syntax(), make.expr_paren(demorganed).syntax());
118127
} else {
119-
edit.replace_ast(neg_expr, demorganed);
128+
editor.replace(neg_expr.syntax(), demorganed.syntax());
120129
};
121130
} else {
122131
cov_mark::hit!(demorgan_double_parens);
123-
edit.replace_ast(paren_expr.into(), add_bang_paren(demorganed));
132+
editor = builder.make_editor(paren_expr.syntax());
133+
134+
editor.replace(paren_expr.syntax(), add_bang_paren(&make, demorganed).syntax());
124135
}
125136
} else {
126-
edit.replace_ast(bin_expr.into(), add_bang_paren(demorganed));
137+
editor = builder.make_editor(bin_expr.syntax());
138+
editor.replace(bin_expr.syntax(), add_bang_paren(&make, demorganed).syntax());
127139
}
140+
141+
editor.add_mappings(make.finish_with_mappings());
142+
builder.add_file_edits(ctx.file_id(), editor);
128143
},
129144
)
130145
}
@@ -161,7 +176,7 @@ pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_>
161176
let (name, arg_expr) = validate_method_call_expr(ctx, &method_call)?;
162177

163178
let ast::Expr::ClosureExpr(closure_expr) = arg_expr else { return None };
164-
let closure_body = closure_expr.body()?;
179+
let closure_body = closure_expr.body()?.clone_for_update();
165180

166181
let op_range = method_call.syntax().text_range();
167182
let label = format!("Apply De Morgan's law to `Iterator::{}`", name.text().as_str());
@@ -170,18 +185,19 @@ pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_>
170185
AssistId("apply_demorgan_iterator", AssistKind::RefactorRewrite),
171186
label,
172187
op_range,
173-
|edit| {
188+
|builder| {
189+
let make = SyntaxFactory::new();
190+
let mut editor = builder.make_editor(method_call.syntax());
174191
// replace the method name
175192
let new_name = match name.text().as_str() {
176-
"all" => make::name_ref("any"),
177-
"any" => make::name_ref("all"),
193+
"all" => make.name_ref("any"),
194+
"any" => make.name_ref("all"),
178195
_ => unreachable!(),
179-
}
180-
.clone_for_update();
181-
edit.replace_ast(name, new_name);
196+
};
197+
editor.replace(name.syntax(), new_name.syntax());
182198

183199
// negate all tail expressions in the closure body
184-
let tail_cb = &mut |e: &_| tail_cb_impl(edit, e);
200+
let tail_cb = &mut |e: &_| tail_cb_impl(&mut editor, &make, e);
185201
walk_expr(&closure_body, &mut |expr| {
186202
if let ast::Expr::ReturnExpr(ret_expr) = expr {
187203
if let Some(ret_expr_arg) = &ret_expr.expr() {
@@ -198,15 +214,15 @@ pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_>
198214
.and_then(ast::PrefixExpr::cast)
199215
.filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not)))
200216
{
201-
edit.delete(
202-
prefix_expr
203-
.op_token()
204-
.expect("prefix expression always has an operator")
205-
.text_range(),
217+
editor.delete(
218+
prefix_expr.op_token().expect("prefix expression always has an operator"),
206219
);
207220
} else {
208-
edit.insert(method_call.syntax().text_range().start(), "!");
221+
editor.insert(Position::before(method_call.syntax()), make.token(SyntaxKind::BANG));
209222
}
223+
224+
editor.add_mappings(make.finish_with_mappings());
225+
builder.add_file_edits(ctx.file_id(), editor);
210226
},
211227
)
212228
}
@@ -233,26 +249,50 @@ fn validate_method_call_expr(
233249
it_type.impls_trait(sema.db, iter_trait, &[]).then_some((name_ref, arg_expr))
234250
}
235251

236-
fn tail_cb_impl(edit: &mut SourceChangeBuilder, e: &ast::Expr) {
252+
fn tail_cb_impl(editor: &mut SyntaxEditor, make: &SyntaxFactory, e: &ast::Expr) {
237253
match e {
238254
ast::Expr::BreakExpr(break_expr) => {
239255
if let Some(break_expr_arg) = break_expr.expr() {
240-
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(edit, e))
256+
for_each_tail_expr(&break_expr_arg, &mut |e| tail_cb_impl(editor, make, e))
241257
}
242258
}
243259
ast::Expr::ReturnExpr(_) => {
244260
// all return expressions have already been handled by the walk loop
245261
}
246262
e => {
247-
let inverted_body = invert_boolean_expression(e.clone());
248-
edit.replace(e.syntax().text_range(), inverted_body.syntax().text());
263+
let inverted_body = invert_boolean_expression(make, e.clone());
264+
editor.replace(e.syntax(), inverted_body.syntax());
249265
}
250266
}
251267
}
252268

253269
/// Add bang and parentheses to the expression.
254-
fn add_bang_paren(expr: ast::Expr) -> ast::Expr {
255-
make::expr_prefix(T![!], make::expr_paren(expr)).into()
270+
fn add_bang_paren(make: &SyntaxFactory, expr: ast::Expr) -> ast::Expr {
271+
make.expr_prefix(T![!], make.expr_paren(expr).into()).into()
272+
}
273+
274+
fn needs_parens_in_place_of(
275+
this: &ast::Expr,
276+
parent: &SyntaxNode,
277+
in_place_of: &ast::Expr,
278+
) -> bool {
279+
assert_eq!(Some(parent), in_place_of.syntax().parent().as_ref());
280+
281+
let child_idx = parent
282+
.children()
283+
.enumerate()
284+
.find_map(|(i, it)| if &it == in_place_of.syntax() { Some(i) } else { None })
285+
.unwrap();
286+
let parent = parent.clone_subtree();
287+
let subtree_place = parent.children().nth(child_idx).unwrap();
288+
289+
let mut editor = SyntaxEditor::new(parent);
290+
editor.replace(subtree_place, this.syntax());
291+
let edit = editor.finish();
292+
293+
let replaced = edit.new_root().children().nth(child_idx).unwrap();
294+
let replaced = ast::Expr::cast(replaced).unwrap();
295+
replaced.needs_parens_in(edit.new_root().clone())
256296
}
257297

258298
#[cfg(test)]

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use syntax::{
1313
};
1414

1515
use crate::{
16-
utils::{invert_boolean_expression, unwrap_trivial_block},
16+
utils::{invert_boolean_expression_legacy, unwrap_trivial_block},
1717
AssistContext, AssistId, AssistKind, Assists,
1818
};
1919

@@ -119,7 +119,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>
119119
| ast::Expr::WhileExpr(_)
120120
| ast::Expr::YieldExpr(_)
121121
);
122-
let cond = if invert_cond { invert_boolean_expression(cond) } else { cond };
122+
let cond = if invert_cond { invert_boolean_expression_legacy(cond) } else { cond };
123123
let cond = if parenthesize { make::expr_paren(cond) } else { cond };
124124
let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
125125
let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use syntax::{
1717

1818
use crate::{
1919
assist_context::{AssistContext, Assists},
20-
utils::invert_boolean_expression,
20+
utils::invert_boolean_expression_legacy,
2121
AssistId, AssistKind,
2222
};
2323

@@ -139,7 +139,7 @@ fn if_expr_to_guarded_return(
139139
let new_expr = {
140140
let then_branch =
141141
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
142-
let cond = invert_boolean_expression(cond_expr);
142+
let cond = invert_boolean_expression_legacy(cond_expr);
143143
make::expr_if(cond, then_branch, None).indent(if_indent_level)
144144
};
145145
new_expr.syntax().clone_for_update()

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use syntax::{
1313

1414
use crate::{
1515
assist_context::{AssistContext, Assists},
16-
utils::invert_boolean_expression,
16+
utils::invert_boolean_expression_legacy,
1717
AssistId, AssistKind,
1818
};
1919

@@ -63,7 +63,7 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>)
6363
let stmts = iter::once(make::expr_stmt(if_expr.into()).into());
6464
make::block_expr(stmts, None)
6565
} else {
66-
let if_cond = invert_boolean_expression(while_cond);
66+
let if_cond = invert_boolean_expression_legacy(while_cond);
6767
let if_expr = make::expr_if(if_cond, break_block, None).syntax().clone().into();
6868
let elements = while_body.stmt_list().map_or_else(
6969
|| Either::Left(iter::empty()),

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use syntax::{
66

77
use crate::{
88
assist_context::{AssistContext, Assists},
9-
utils::invert_boolean_expression,
9+
utils::invert_boolean_expression_legacy,
1010
AssistId, AssistKind,
1111
};
1212

@@ -48,7 +48,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()
4848
};
4949

5050
acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
51-
let flip_cond = invert_boolean_expression(cond.clone());
51+
let flip_cond = invert_boolean_expression_legacy(cond.clone());
5252
edit.replace_ast(cond, flip_cond);
5353

5454
let else_node = else_block.syntax();

crates/ide-assists/src/utils.rs

+74-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ use syntax::{
1717
self,
1818
edit::{AstNodeEdit, IndentLevel},
1919
edit_in_place::{AttrsOwnerEdit, Indent, Removable},
20-
make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
20+
make,
21+
syntax_factory::SyntaxFactory,
22+
HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
2123
},
2224
ted, AstNode, AstToken, Direction, Edition, NodeOrToken, SourceFile,
2325
SyntaxKind::*,
@@ -245,11 +247,79 @@ pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
245247
.unwrap_or_else(|| node.text_range().start())
246248
}
247249

248-
pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
249-
invert_special_case(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr).into())
250+
pub(crate) fn invert_boolean_expression(make: &SyntaxFactory, expr: ast::Expr) -> ast::Expr {
251+
invert_special_case(make, &expr).unwrap_or_else(|| make.expr_prefix(T![!], expr).into())
250252
}
251253

252-
fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
254+
// FIXME: Migrate usages of this function to the above function and remove this.
255+
pub(crate) fn invert_boolean_expression_legacy(expr: ast::Expr) -> ast::Expr {
256+
invert_special_case_legacy(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr).into())
257+
}
258+
259+
fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Expr> {
260+
match expr {
261+
ast::Expr::BinExpr(bin) => {
262+
let op_kind = bin.op_kind()?;
263+
let rev_kind = match op_kind {
264+
ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated }) => {
265+
ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: !negated })
266+
}
267+
ast::BinaryOp::CmpOp(ast::CmpOp::Ord { ordering: ast::Ordering::Less, strict }) => {
268+
ast::BinaryOp::CmpOp(ast::CmpOp::Ord {
269+
ordering: ast::Ordering::Greater,
270+
strict: !strict,
271+
})
272+
}
273+
ast::BinaryOp::CmpOp(ast::CmpOp::Ord {
274+
ordering: ast::Ordering::Greater,
275+
strict,
276+
}) => ast::BinaryOp::CmpOp(ast::CmpOp::Ord {
277+
ordering: ast::Ordering::Less,
278+
strict: !strict,
279+
}),
280+
// Parenthesize other expressions before prefixing `!`
281+
_ => {
282+
return Some(
283+
make.expr_prefix(T![!], make.expr_paren(expr.clone()).into()).into(),
284+
);
285+
}
286+
};
287+
288+
Some(make.expr_bin(bin.lhs()?, rev_kind, bin.rhs()?).into())
289+
}
290+
ast::Expr::MethodCallExpr(mce) => {
291+
let receiver = mce.receiver()?;
292+
let method = mce.name_ref()?;
293+
let arg_list = mce.arg_list()?;
294+
295+
let method = match method.text().as_str() {
296+
"is_some" => "is_none",
297+
"is_none" => "is_some",
298+
"is_ok" => "is_err",
299+
"is_err" => "is_ok",
300+
_ => return None,
301+
};
302+
303+
Some(make.expr_method_call(receiver, make.name_ref(method), arg_list).into())
304+
}
305+
ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? {
306+
ast::Expr::ParenExpr(parexpr) => {
307+
parexpr.expr().map(|e| e.clone_subtree().clone_for_update())
308+
}
309+
_ => pe.expr().map(|e| e.clone_subtree().clone_for_update()),
310+
},
311+
ast::Expr::Literal(lit) => match lit.kind() {
312+
ast::LiteralKind::Bool(b) => match b {
313+
true => Some(ast::Expr::Literal(make.expr_literal("false"))),
314+
false => Some(ast::Expr::Literal(make.expr_literal("true"))),
315+
},
316+
_ => None,
317+
},
318+
_ => None,
319+
}
320+
}
321+
322+
fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
253323
match expr {
254324
ast::Expr::BinExpr(bin) => {
255325
let bin = bin.clone_for_update();

0 commit comments

Comments
 (0)