|
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::{ |
@@ -37,103 +37,101 @@ use crate::{ |
37 | 37 | // ``` |
38 | 38 | pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
39 | 39 | let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; |
40 | | - let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment { |
41 | | - assign_expr.lhs()? |
42 | | - } else { |
| 40 | + |
| 41 | + let op_kind = assign_expr.op_kind()?; |
| 42 | + if op_kind != ast::BinOp::Assignment { |
| 43 | + cov_mark::hit!(test_cant_pull_non_assignments); |
43 | 44 | return None; |
| 45 | + } |
| 46 | + |
| 47 | + let mut collector = AssignmentsCollector { |
| 48 | + sema: &ctx.sema, |
| 49 | + common_lhs: assign_expr.lhs()?, |
| 50 | + assignments: Vec::new(), |
44 | 51 | }; |
45 | 52 |
|
46 | | - let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { |
47 | | - ( |
48 | | - ast::Expr::cast(if_expr.syntax().to_owned())?, |
49 | | - exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()), |
50 | | - ) |
| 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() |
51 | 56 | } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() { |
52 | | - ( |
53 | | - ast::Expr::cast(match_expr.syntax().to_owned())?, |
54 | | - exprify_match(&match_expr, &ctx.sema, &name_expr)?, |
55 | | - ) |
| 57 | + collector.collect_match(&match_expr)?; |
| 58 | + match_expr.into() |
56 | 59 | } else { |
57 | 60 | return None; |
58 | 61 | }; |
59 | 62 |
|
60 | | - let expr_stmt = make::expr_stmt(new_stmt); |
61 | | - |
62 | 63 | acc.add( |
63 | 64 | AssistId("pull_assignment_up", AssistKind::RefactorExtract), |
64 | 65 | "Pull assignment up", |
65 | | - old_stmt.syntax().text_range(), |
| 66 | + tgt.syntax().text_range(), |
66 | 67 | move |edit| { |
67 | | - 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()); |
68 | 83 | }, |
69 | 84 | ) |
70 | 85 | } |
71 | 86 |
|
72 | | -fn exprify_match( |
73 | | - match_expr: &ast::MatchExpr, |
74 | | - sema: &hir::Semantics<ide_db::RootDatabase>, |
75 | | - name: &ast::Expr, |
76 | | -) -> Option<ast::Expr> { |
77 | | - let new_arm_list = match_expr |
78 | | - .match_arm_list()? |
79 | | - .arms() |
80 | | - .map(|arm| { |
81 | | - if let ast::Expr::BlockExpr(block) = arm.expr()? { |
82 | | - let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level()); |
83 | | - Some(arm.replace_descendant(block, new_block)) |
84 | | - } else { |
85 | | - None |
86 | | - } |
87 | | - }) |
88 | | - .collect::<Option<Vec<_>>>()?; |
89 | | - let new_arm_list = match_expr |
90 | | - .match_arm_list()? |
91 | | - .replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list)); |
92 | | - 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)>, |
93 | 91 | } |
94 | 92 |
|
95 | | -fn exprify_if( |
96 | | - statement: &ast::IfExpr, |
97 | | - sema: &hir::Semantics<ide_db::RootDatabase>, |
98 | | - name: &ast::Expr, |
99 | | -) -> Option<ast::Expr> { |
100 | | - let then_branch = exprify_block(&statement.then_branch()?, sema, name)?; |
101 | | - let else_branch = match statement.else_branch()? { |
102 | | - ast::ElseBranch::Block(ref block) => { |
103 | | - ast::ElseBranch::Block(exprify_block(block, sema, name)?) |
104 | | - } |
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 | + _ => return None, |
| 99 | + } |
110 | 100 | } |
111 | | - }; |
112 | | - Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch))) |
113 | | -} |
114 | 101 |
|
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; |
| 102 | + Some(()) |
122 | 103 | } |
| 104 | + fn collect_if(&mut self, if_expr: &ast::IfExpr) -> Option<()> { |
| 105 | + let then_branch = if_expr.then_branch()?; |
| 106 | + self.collect_block(&then_branch)?; |
| 107 | + |
| 108 | + match if_expr.else_branch()? { |
| 109 | + ast::ElseBranch::Block(block) => self.collect_block(&block), |
| 110 | + ast::ElseBranch::IfExpr(expr) => { |
| 111 | + cov_mark::hit!(test_pull_assignment_up_chained_if); |
| 112 | + self.collect_if(&expr) |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> { |
| 117 | + if block.tail_expr().is_some() { |
| 118 | + return None; |
| 119 | + } |
123 | 120 |
|
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()?))); |
| 121 | + let last_stmt = block.statements().last()?; |
| 122 | + if let ast::Stmt::ExprStmt(stmt) = last_stmt { |
| 123 | + if let ast::Expr::BinExpr(expr) = stmt.expr()? { |
| 124 | + if expr.op_kind()? == ast::BinOp::Assignment |
| 125 | + && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs) |
| 126 | + { |
| 127 | + self.assignments.push((stmt, expr.rhs()?)); |
| 128 | + return Some(()); |
| 129 | + } |
133 | 130 | } |
134 | 131 | } |
| 132 | + |
| 133 | + None |
135 | 134 | } |
136 | | - None |
137 | 135 | } |
138 | 136 |
|
139 | 137 | fn is_equivalent( |
@@ -242,6 +240,38 @@ fn foo() { |
242 | 240 | ); |
243 | 241 | } |
244 | 242 |
|
| 243 | + #[test] |
| 244 | + #[ignore] |
| 245 | + fn test_pull_assignment_up_assignment_expressions() { |
| 246 | + check_assist( |
| 247 | + pull_assignment_up, |
| 248 | + r#" |
| 249 | +fn foo() { |
| 250 | + let mut a = 1; |
| 251 | +
|
| 252 | + match 1 { |
| 253 | + 1 => { $0a = 2; }, |
| 254 | + 2 => a = 3, |
| 255 | + 3 => { |
| 256 | + a = 4 |
| 257 | + } |
| 258 | + } |
| 259 | +}"#, |
| 260 | + r#" |
| 261 | +fn foo() { |
| 262 | + let mut a = 1; |
| 263 | +
|
| 264 | + a = match 1 { |
| 265 | + 1 => { 2 }, |
| 266 | + 2 => 3, |
| 267 | + 3 => { |
| 268 | + 4 |
| 269 | + } |
| 270 | + }; |
| 271 | +}"#, |
| 272 | + ); |
| 273 | + } |
| 274 | + |
245 | 275 | #[test] |
246 | 276 | fn test_pull_assignment_up_not_last_not_applicable() { |
247 | 277 | check_assist_not_applicable( |
@@ -436,6 +466,26 @@ fn foo() { |
436 | 466 | 3 |
437 | 467 | }; |
438 | 468 | } |
| 469 | +"#, |
| 470 | + ) |
| 471 | + } |
| 472 | + |
| 473 | + #[test] |
| 474 | + fn test_cant_pull_non_assignments() { |
| 475 | + cov_mark::check!(test_cant_pull_non_assignments); |
| 476 | + check_assist_not_applicable( |
| 477 | + pull_assignment_up, |
| 478 | + r#" |
| 479 | +fn foo() { |
| 480 | + let mut a = 1; |
| 481 | + let b = &mut a; |
| 482 | +
|
| 483 | + if true { |
| 484 | + $0*b + 2; |
| 485 | + } else { |
| 486 | + *b + 3; |
| 487 | + } |
| 488 | +} |
439 | 489 | "#, |
440 | 490 | ) |
441 | 491 | } |
|
0 commit comments