internal: pull_assignment_up uses mutable trees

This commit is contained in:
Aleksey Kladov 2021-05-08 23:09:36 +03:00
parent e603090961
commit 1755b57e1a
2 changed files with 72 additions and 70 deletions

View file

@ -1,6 +1,6 @@
use syntax::{ use syntax::{
ast::{self, edit::AstNodeEdit, make}, ast::{self, make},
AstNode, ted, AstNode,
}; };
use crate::{ use crate::{
@ -44,96 +44,95 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Opti
return None; return None;
} }
let name_expr = assign_expr.lhs()?; let mut collector = AssignmentsCollector {
sema: &ctx.sema,
common_lhs: assign_expr.lhs()?,
assignments: Vec::new(),
};
let old_stmt: ast::Expr; let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
let new_stmt: ast::Expr; collector.collect_if(&if_expr)?;
if_expr.into()
if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
new_stmt = exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level());
old_stmt = if_expr.into();
} else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() { } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
new_stmt = exprify_match(&match_expr, &ctx.sema, &name_expr)?; collector.collect_match(&match_expr)?;
old_stmt = match_expr.into() match_expr.into()
} else { } else {
return None; return None;
}; };
let expr_stmt = make::expr_stmt(new_stmt);
acc.add( acc.add(
AssistId("pull_assignment_up", AssistKind::RefactorExtract), AssistId("pull_assignment_up", AssistKind::RefactorExtract),
"Pull assignment up", "Pull assignment up",
old_stmt.syntax().text_range(), tgt.syntax().text_range(),
move |edit| { move |edit| {
edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt)); let assignments: Vec<_> = collector
.assignments
.into_iter()
.map(|(stmt, rhs)| (edit.make_ast_mut(stmt), rhs.clone_for_update()))
.collect();
let tgt = edit.make_ast_mut(tgt);
for (stmt, rhs) in assignments {
ted::replace(stmt.syntax(), rhs.syntax());
}
let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone());
let assign_stmt = make::expr_stmt(assign_expr);
ted::replace(tgt.syntax(), assign_stmt.syntax().clone_for_update());
}, },
) )
} }
fn exprify_match( struct AssignmentsCollector<'a> {
match_expr: &ast::MatchExpr, sema: &'a hir::Semantics<'a, ide_db::RootDatabase>,
sema: &hir::Semantics<ide_db::RootDatabase>, common_lhs: ast::Expr,
name: &ast::Expr, assignments: Vec<(ast::ExprStmt, ast::Expr)>,
) -> Option<ast::Expr> { }
let new_arm_list = match_expr
.match_arm_list()? impl<'a> AssignmentsCollector<'a> {
.arms() fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> {
.map(|arm| { for arm in match_expr.match_arm_list()?.arms() {
if let ast::Expr::BlockExpr(block) = arm.expr()? { match arm.expr()? {
let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level()); ast::Expr::BlockExpr(block) => self.collect_block(&block)?,
Some(arm.replace_descendant(block, new_block)) // TODO: Handle this while we are at it?
} else { _ => return None,
None
} }
})
.collect::<Option<Vec<_>>>()?;
let new_arm_list = match_expr
.match_arm_list()?
.replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list));
Some(make::expr_match(match_expr.expr()?, new_arm_list))
}
fn exprify_if(
statement: &ast::IfExpr,
sema: &hir::Semantics<ide_db::RootDatabase>,
name: &ast::Expr,
) -> Option<ast::Expr> {
let then_branch = exprify_block(&statement.then_branch()?, sema, name)?;
let else_branch = match statement.else_branch()? {
ast::ElseBranch::Block(block) => ast::ElseBranch::Block(exprify_block(&block, sema, name)?),
ast::ElseBranch::IfExpr(expr) => {
cov_mark::hit!(test_pull_assignment_up_chained_if);
ast::ElseBranch::IfExpr(ast::IfExpr::cast(
exprify_if(&expr, sema, name)?.syntax().to_owned(),
)?)
} }
};
Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
}
fn exprify_block( Some(())
block: &ast::BlockExpr,
sema: &hir::Semantics<ide_db::RootDatabase>,
name: &ast::Expr,
) -> Option<ast::BlockExpr> {
if block.tail_expr().is_some() {
return None;
} }
fn collect_if(&mut self, if_expr: &ast::IfExpr) -> Option<()> {
let then_branch = if_expr.then_branch()?;
self.collect_block(&then_branch)?;
let mut stmts: Vec<_> = block.statements().collect(); match if_expr.else_branch()? {
let stmt = stmts.pop()?; ast::ElseBranch::Block(block) => self.collect_block(&block),
ast::ElseBranch::IfExpr(expr) => {
if let ast::Stmt::ExprStmt(stmt) = stmt { cov_mark::hit!(test_pull_assignment_up_chained_if);
if let ast::Expr::BinExpr(expr) = stmt.expr()? { self.collect_if(&expr)
if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name)
{
// The last statement in the block is an assignment to the name we want
return Some(make::block_expr(stmts, Some(expr.rhs()?)));
} }
} }
} }
None fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> {
if block.tail_expr().is_some() {
return None;
}
let last_stmt = block.statements().last()?;
if let ast::Stmt::ExprStmt(stmt) = last_stmt {
if let ast::Expr::BinExpr(expr) = stmt.expr()? {
if expr.op_kind()? == ast::BinOp::Assignment
&& is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
{
self.assignments.push((stmt, expr.rhs()?));
return Some(());
}
}
}
None
}
} }
fn is_equivalent( fn is_equivalent(

View file

@ -275,6 +275,9 @@ pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr {
let expr = elements.into_iter().format(", "); let expr = elements.into_iter().format(", ");
expr_from_text(&format!("({})", expr)) expr_from_text(&format!("({})", expr))
} }
pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
expr_from_text(&format!("{} = {}", lhs, rhs))
}
fn expr_from_text(text: &str) -> ast::Expr { fn expr_from_text(text: &str) -> ast::Expr {
ast_from_text(&format!("const C: () = {};", text)) ast_from_text(&format!("const C: () = {};", text))
} }