Skip to content

internal: Migrate convert_bool_then to SyntaxEditor #19253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 52 additions & 30 deletions crates/ide-assists/src/handlers/convert_bool_then.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ use ide_db::{
};
use itertools::Itertools;
use syntax::{
ast::{self, edit::AstNodeEdit, make, HasArgList},
ted, AstNode, SyntaxNode,
ast::{self, edit::AstNodeEdit, syntax_factory::SyntaxFactory, HasArgList},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does still use AstNodeEdit for indentation, I assume that's mainly to not regress indentation for now? (merging either way)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, currently we have two ways to indent things (except manually indenting all lines in a syntax node):

/// Soft-deprecated in favor of mutable tree editing API `edit_in_place::Ident`.
pub trait AstNodeEdit: AstNode + Clone + Sized {
fn indent_level(&self) -> IndentLevel {
IndentLevel::from_node(self.syntax())
}
#[must_use]
fn indent(&self, level: IndentLevel) -> Self {
fn indent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode {
let res = node.clone_subtree().clone_for_update();
level.increase_indent(&res);
res.clone_subtree()
}
Self::cast(indent_inner(self.syntax(), level)).unwrap()
}

pub trait Indent: AstNode + Clone + Sized {
fn indent_level(&self) -> IndentLevel {
IndentLevel::from_node(self.syntax())
}
fn indent(&self, by: IndentLevel) {
by.increase_indent(self.syntax());
}

and both of them utilizes mutable syntax trees.l

I'll add a non-mutable syntax tree alternative and replace previous usages with it in next PR 😄

syntax_editor::SyntaxEditor,
AstNode, SyntaxNode,
};

use crate::{
utils::{invert_boolean_expression_legacy, unwrap_trivial_block},
utils::{invert_boolean_expression, unwrap_trivial_block},
AssistContext, AssistId, AssistKind, Assists,
};

Expand Down Expand Up @@ -76,9 +77,9 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>
"Convert `if` expression to `bool::then` call",
target,
|builder| {
let closure_body = closure_body.clone_for_update();
let closure_body = closure_body.clone_subtree();
let mut editor = SyntaxEditor::new(closure_body.syntax().clone());
// Rewrite all `Some(e)` in tail position to `e`
let mut replacements = Vec::new();
for_each_tail_expr(&closure_body, &mut |e| {
let e = match e {
ast::Expr::BreakExpr(e) => e.expr(),
Expand All @@ -88,12 +89,16 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>
if let Some(ast::Expr::CallExpr(call)) = e {
if let Some(arg_list) = call.arg_list() {
if let Some(arg) = arg_list.args().next() {
replacements.push((call.syntax().clone(), arg.syntax().clone()));
editor.replace(call.syntax(), arg.syntax());
}
}
}
});
replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
let edit = editor.finish();
let closure_body = ast::Expr::cast(edit.new_root().clone()).unwrap();

let mut editor = builder.make_editor(expr.syntax());
let make = SyntaxFactory::new();
let closure_body = match closure_body {
ast::Expr::BlockExpr(block) => unwrap_trivial_block(block),
e => e,
Expand All @@ -119,11 +124,18 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_>
| ast::Expr::WhileExpr(_)
| ast::Expr::YieldExpr(_)
);
let cond = if invert_cond { invert_boolean_expression_legacy(cond) } else { cond };
let cond = if parenthesize { make::expr_paren(cond) } else { cond };
let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);
builder.replace(target, mcall.to_string());
let cond = if invert_cond {
invert_boolean_expression(&make, cond)
} else {
cond.clone_for_update()
};
let cond = if parenthesize { make.expr_paren(cond).into() } else { cond };
let arg_list = make.arg_list(Some(make.expr_closure(None, closure_body).into()));
let mcall = make.expr_method_call(cond, make.name_ref("then"), arg_list);
editor.replace(expr.syntax(), mcall.syntax());

editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.file_id(), editor);
},
)
}
Expand Down Expand Up @@ -173,45 +185,55 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_>
"Convert `bool::then` call to `if`",
target,
|builder| {
let closure_body = match closure_body {
let mapless_make = SyntaxFactory::without_mappings();
let closure_body = match closure_body.reset_indent() {
ast::Expr::BlockExpr(block) => block,
e => make::block_expr(None, Some(e)),
e => mapless_make.block_expr(None, Some(e)),
};

let closure_body = closure_body.clone_for_update();
let closure_body = closure_body.clone_subtree();
let mut editor = SyntaxEditor::new(closure_body.syntax().clone());
// Wrap all tails in `Some(...)`
let none_path = make::expr_path(make::ext::ident_path("None"));
let some_path = make::expr_path(make::ext::ident_path("Some"));
let mut replacements = Vec::new();
let none_path = mapless_make.expr_path(mapless_make.ident_path("None"));
let some_path = mapless_make.expr_path(mapless_make.ident_path("Some"));
for_each_tail_expr(&ast::Expr::BlockExpr(closure_body.clone()), &mut |e| {
let e = match e {
ast::Expr::BreakExpr(e) => e.expr(),
ast::Expr::ReturnExpr(e) => e.expr(),
_ => Some(e.clone()),
};
if let Some(expr) = e {
replacements.push((
editor.replace(
expr.syntax().clone(),
make::expr_call(some_path.clone(), make::arg_list(Some(expr)))
mapless_make
.expr_call(some_path.clone(), mapless_make.arg_list(Some(expr)))
.syntax()
.clone_for_update(),
));
.clone(),
);
}
});
replacements.into_iter().for_each(|(old, new)| ted::replace(old, new));
let edit = editor.finish();
let closure_body = ast::BlockExpr::cast(edit.new_root().clone()).unwrap();

let mut editor = builder.make_editor(mcall.syntax());
let make = SyntaxFactory::new();

let cond = match &receiver {
ast::Expr::ParenExpr(expr) => expr.expr().unwrap_or(receiver),
_ => receiver,
};
let if_expr = make::expr_if(
cond,
closure_body.reset_indent(),
Some(ast::ElseBranch::Block(make::block_expr(None, Some(none_path)))),
)
.indent(mcall.indent_level());
let if_expr = make
.expr_if(
cond,
closure_body,
Some(ast::ElseBranch::Block(make.block_expr(None, Some(none_path)))),
)
.indent(mcall.indent_level())
.clone_for_update();
editor.replace(mcall.syntax().clone(), if_expr.syntax().clone());

builder.replace(target, if_expr.to_string());
editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.file_id(), editor);
},
)
}
Expand Down
Loading
Loading