aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs137
-rw-r--r--crates/syntax/src/ast/make.rs3
2 files changed, 71 insertions, 69 deletions
diff --git a/crates/ide_assists/src/handlers/pull_assignment_up.rs b/crates/ide_assists/src/handlers/pull_assignment_up.rs
index 543b1dfe9..602c3813e 100644
--- a/crates/ide_assists/src/handlers/pull_assignment_up.rs
+++ b/crates/ide_assists/src/handlers/pull_assignment_up.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, edit::AstNodeEdit, make}, 2 ast::{self, make},
3 AstNode, 3 ted, AstNode,
4}; 4};
5 5
6use crate::{ 6use crate::{
@@ -44,96 +44,95 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Opti
44 return None; 44 return None;
45 } 45 }
46 46
47 let name_expr = assign_expr.lhs()?; 47 let mut collector = AssignmentsCollector {
48 48 sema: &ctx.sema,
49 let old_stmt: ast::Expr; 49 common_lhs: assign_expr.lhs()?,
50 let new_stmt: ast::Expr; 50 assignments: Vec::new(),
51 };
51 52
52 if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { 53 let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
53 new_stmt = exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()); 54 collector.collect_if(&if_expr)?;
54 old_stmt = if_expr.into(); 55 if_expr.into()
55 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() { 56 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
56 new_stmt = exprify_match(&match_expr, &ctx.sema, &name_expr)?; 57 collector.collect_match(&match_expr)?;
57 old_stmt = match_expr.into() 58 match_expr.into()
58 } else { 59 } else {
59 return None; 60 return None;
60 }; 61 };
61 62
62 let expr_stmt = make::expr_stmt(new_stmt);
63
64 acc.add( 63 acc.add(
65 AssistId("pull_assignment_up", AssistKind::RefactorExtract), 64 AssistId("pull_assignment_up", AssistKind::RefactorExtract),
66 "Pull assignment up", 65 "Pull assignment up",
67 old_stmt.syntax().text_range(), 66 tgt.syntax().text_range(),
68 move |edit| { 67 move |edit| {
69 edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt)); 68 let assignments: Vec<_> = collector
69 .assignments
70 .into_iter()
71 .map(|(stmt, rhs)| (edit.make_ast_mut(stmt), rhs.clone_for_update()))
72 .collect();
73
74 let tgt = edit.make_ast_mut(tgt);
75
76 for (stmt, rhs) in assignments {
77 ted::replace(stmt.syntax(), rhs.syntax());
78 }
79 let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone());
80 let assign_stmt = make::expr_stmt(assign_expr);
81
82 ted::replace(tgt.syntax(), assign_stmt.syntax().clone_for_update());
70 }, 83 },
71 ) 84 )
72} 85}
73 86
74fn exprify_match( 87struct AssignmentsCollector<'a> {
75 match_expr: &ast::MatchExpr, 88 sema: &'a hir::Semantics<'a, ide_db::RootDatabase>,
76 sema: &hir::Semantics<ide_db::RootDatabase>, 89 common_lhs: ast::Expr,
77 name: &ast::Expr, 90 assignments: Vec<(ast::ExprStmt, ast::Expr)>,
78) -> Option<ast::Expr> {
79 let new_arm_list = match_expr
80 .match_arm_list()?
81 .arms()
82 .map(|arm| {
83 if let ast::Expr::BlockExpr(block) = arm.expr()? {
84 let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level());
85 Some(arm.replace_descendant(block, new_block))
86 } else {
87 None
88 }
89 })
90 .collect::<Option<Vec<_>>>()?;
91 let new_arm_list = match_expr
92 .match_arm_list()?
93 .replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list));
94 Some(make::expr_match(match_expr.expr()?, new_arm_list))
95} 91}
96 92
97fn exprify_if( 93impl<'a> AssignmentsCollector<'a> {
98 statement: &ast::IfExpr, 94 fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> {
99 sema: &hir::Semantics<ide_db::RootDatabase>, 95 for arm in match_expr.match_arm_list()?.arms() {
100 name: &ast::Expr, 96 match arm.expr()? {
101) -> Option<ast::Expr> { 97 ast::Expr::BlockExpr(block) => self.collect_block(&block)?,
102 let then_branch = exprify_block(&statement.then_branch()?, sema, name)?; 98 // TODO: Handle this while we are at it?
103 let else_branch = match statement.else_branch()? { 99 _ => return None,
104 ast::ElseBranch::Block(block) => ast::ElseBranch::Block(exprify_block(&block, sema, name)?), 100 }
105 ast::ElseBranch::IfExpr(expr) => {
106 cov_mark::hit!(test_pull_assignment_up_chained_if);
107 ast::ElseBranch::IfExpr(ast::IfExpr::cast(
108 exprify_if(&expr, sema, name)?.syntax().to_owned(),
109 )?)
110 } 101 }
111 };
112 Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
113}
114 102
115fn exprify_block( 103 Some(())
116 block: &ast::BlockExpr,
117 sema: &hir::Semantics<ide_db::RootDatabase>,
118 name: &ast::Expr,
119) -> Option<ast::BlockExpr> {
120 if block.tail_expr().is_some() {
121 return None;
122 } 104 }
105 fn collect_if(&mut self, if_expr: &ast::IfExpr) -> Option<()> {
106 let then_branch = if_expr.then_branch()?;
107 self.collect_block(&then_branch)?;
108
109 match if_expr.else_branch()? {
110 ast::ElseBranch::Block(block) => self.collect_block(&block),
111 ast::ElseBranch::IfExpr(expr) => {
112 cov_mark::hit!(test_pull_assignment_up_chained_if);
113 self.collect_if(&expr)
114 }
115 }
116 }
117 fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> {
118 if block.tail_expr().is_some() {
119 return None;
120 }
123 121
124 let mut stmts: Vec<_> = block.statements().collect(); 122 let last_stmt = block.statements().last()?;
125 let stmt = stmts.pop()?; 123 if let ast::Stmt::ExprStmt(stmt) = last_stmt {
126 124 if let ast::Expr::BinExpr(expr) = stmt.expr()? {
127 if let ast::Stmt::ExprStmt(stmt) = stmt { 125 if expr.op_kind()? == ast::BinOp::Assignment
128 if let ast::Expr::BinExpr(expr) = stmt.expr()? { 126 && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
129 if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name) 127 {
130 { 128 self.assignments.push((stmt, expr.rhs()?));
131 // The last statement in the block is an assignment to the name we want 129 return Some(());
132 return Some(make::block_expr(stmts, Some(expr.rhs()?))); 130 }
133 } 131 }
134 } 132 }
133
134 None
135 } 135 }
136 None
137} 136}
138 137
139fn is_equivalent( 138fn is_equivalent(
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index f8b508a90..5a6687397 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -275,6 +275,9 @@ pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr {
275 let expr = elements.into_iter().format(", "); 275 let expr = elements.into_iter().format(", ");
276 expr_from_text(&format!("({})", expr)) 276 expr_from_text(&format!("({})", expr))
277} 277}
278pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
279 expr_from_text(&format!("{} = {}", lhs, rhs))
280}
278fn expr_from_text(text: &str) -> ast::Expr { 281fn expr_from_text(text: &str) -> ast::Expr {
279 ast_from_text(&format!("const C: () = {};", text)) 282 ast_from_text(&format!("const C: () = {};", text))
280} 283}