|
1 | 1 | use syntax::{ |
2 | | - ast::{self, edit::AstNodeEdit, make}, |
3 | | - AstNode, |
| 2 | + ast::{self, make}, |
| 3 | + ted, AstNode, |
4 | 4 | }; |
5 | 5 |
|
6 | 6 | use crate::{ |
@@ -44,96 +44,95 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Opti |
44 | 44 | return None; |
45 | 45 | } |
46 | 46 |
|
47 | | - let name_expr = assign_expr.lhs()?; |
48 | | - |
49 | | - let old_stmt: ast::Expr; |
50 | | - let new_stmt: ast::Expr; |
| 47 | + let mut collector = AssignmentsCollector { |
| 48 | + sema: &ctx.sema, |
| 49 | + common_lhs: assign_expr.lhs()?, |
| 50 | + assignments: Vec::new(), |
| 51 | + }; |
51 | 52 |
|
52 | | - 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 | | - old_stmt = if_expr.into(); |
| 53 | + let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { |
| 54 | + collector.collect_if(&if_expr)?; |
| 55 | + if_expr.into() |
55 | 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 | | - old_stmt = match_expr.into() |
| 57 | + collector.collect_match(&match_expr)?; |
| 58 | + match_expr.into() |
58 | 59 | } else { |
59 | 60 | return None; |
60 | 61 | }; |
61 | 62 |
|
62 | | - let expr_stmt = make::expr_stmt(new_stmt); |
63 | | - |
64 | 63 | acc.add( |
65 | 64 | AssistId("pull_assignment_up", AssistKind::RefactorExtract), |
66 | 65 | "Pull assignment up", |
67 | | - old_stmt.syntax().text_range(), |
| 66 | + tgt.syntax().text_range(), |
68 | 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 |
|
74 | | -fn exprify_match( |
75 | | - match_expr: &ast::MatchExpr, |
76 | | - sema: &hir::Semantics<ide_db::RootDatabase>, |
77 | | - name: &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)) |
| 87 | +struct AssignmentsCollector<'a> { |
| 88 | + sema: &'a hir::Semantics<'a, ide_db::RootDatabase>, |
| 89 | + common_lhs: ast::Expr, |
| 90 | + assignments: Vec<(ast::ExprStmt, ast::Expr)>, |
95 | 91 | } |
96 | 92 |
|
97 | | -fn exprify_if( |
98 | | - statement: &ast::IfExpr, |
99 | | - sema: &hir::Semantics<ide_db::RootDatabase>, |
100 | | - name: &ast::Expr, |
101 | | -) -> Option<ast::Expr> { |
102 | | - let then_branch = exprify_block(&statement.then_branch()?, sema, name)?; |
103 | | - let else_branch = match statement.else_branch()? { |
104 | | - ast::ElseBranch::Block(block) => ast::ElseBranch::Block(exprify_block(&block, sema, name)?), |
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 | | - )?) |
| 93 | +impl<'a> AssignmentsCollector<'a> { |
| 94 | + fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> { |
| 95 | + for arm in match_expr.match_arm_list()?.arms() { |
| 96 | + match arm.expr()? { |
| 97 | + ast::Expr::BlockExpr(block) => self.collect_block(&block)?, |
| 98 | + // TODO: Handle this while we are at it? |
| 99 | + _ => return None, |
| 100 | + } |
110 | 101 | } |
111 | | - }; |
112 | | - Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch))) |
113 | | -} |
114 | 102 |
|
115 | | -fn exprify_block( |
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; |
| 103 | + Some(()) |
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(); |
125 | | - let stmt = stmts.pop()?; |
126 | | - |
127 | | - if let ast::Stmt::ExprStmt(stmt) = stmt { |
128 | | - if let ast::Expr::BinExpr(expr) = stmt.expr()? { |
129 | | - if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name) |
130 | | - { |
131 | | - // The last statement in the block is an assignment to the name we want |
132 | | - return Some(make::block_expr(stmts, Some(expr.rhs()?))); |
| 122 | + let last_stmt = block.statements().last()?; |
| 123 | + if let ast::Stmt::ExprStmt(stmt) = last_stmt { |
| 124 | + if let ast::Expr::BinExpr(expr) = stmt.expr()? { |
| 125 | + if expr.op_kind()? == ast::BinOp::Assignment |
| 126 | + && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs) |
| 127 | + { |
| 128 | + self.assignments.push((stmt, expr.rhs()?)); |
| 129 | + return Some(()); |
| 130 | + } |
133 | 131 | } |
134 | 132 | } |
| 133 | + |
| 134 | + None |
135 | 135 | } |
136 | | - None |
137 | 136 | } |
138 | 137 |
|
139 | 138 | fn is_equivalent( |
|
0 commit comments