diff options
Diffstat (limited to 'crates/ide_assists/src/handlers/pull_assignment_up.rs')
-rw-r--r-- | crates/ide_assists/src/handlers/pull_assignment_up.rs | 212 |
1 files changed, 140 insertions, 72 deletions
diff --git a/crates/ide_assists/src/handlers/pull_assignment_up.rs b/crates/ide_assists/src/handlers/pull_assignment_up.rs index 04bae4e58..f07b8a6c0 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 @@ | |||
1 | use syntax::{ | 1 | use syntax::{ |
2 | ast::{self, edit::AstNodeEdit, make}, | 2 | ast::{self, make}, |
3 | AstNode, | 3 | ted, AstNode, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{ | 6 | use crate::{ |
@@ -37,103 +37,120 @@ use crate::{ | |||
37 | // ``` | 37 | // ``` |
38 | pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 38 | pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
39 | let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; | 39 | let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; |
40 | let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment { | 40 | |
41 | assign_expr.lhs()? | 41 | let op_kind = assign_expr.op_kind()?; |
42 | } else { | 42 | if op_kind != ast::BinOp::Assignment { |
43 | cov_mark::hit!(test_cant_pull_non_assignments); | ||
43 | return None; | 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>() { | 53 | let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { |
47 | ( | 54 | collector.collect_if(&if_expr)?; |
48 | ast::Expr::cast(if_expr.syntax().to_owned())?, | 55 | if_expr.into() |
49 | exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()), | ||
50 | ) | ||
51 | } 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>() { |
52 | ( | 57 | collector.collect_match(&match_expr)?; |
53 | ast::Expr::cast(match_expr.syntax().to_owned())?, | 58 | match_expr.into() |
54 | exprify_match(&match_expr, &ctx.sema, &name_expr)?, | ||
55 | ) | ||
56 | } else { | 59 | } else { |
57 | return None; | 60 | return None; |
58 | }; | 61 | }; |
59 | 62 | ||
60 | let expr_stmt = make::expr_stmt(new_stmt); | 63 | if let Some(parent) = tgt.syntax().parent() { |
64 | if matches!(parent.kind(), syntax::SyntaxKind::BIN_EXPR | syntax::SyntaxKind::LET_STMT) { | ||
65 | return None; | ||
66 | } | ||
67 | } | ||
61 | 68 | ||
62 | acc.add( | 69 | acc.add( |
63 | AssistId("pull_assignment_up", AssistKind::RefactorExtract), | 70 | AssistId("pull_assignment_up", AssistKind::RefactorExtract), |
64 | "Pull assignment up", | 71 | "Pull assignment up", |
65 | old_stmt.syntax().text_range(), | 72 | tgt.syntax().text_range(), |
66 | move |edit| { | 73 | move |edit| { |
67 | edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt)); | 74 | let assignments: Vec<_> = collector |
75 | .assignments | ||
76 | .into_iter() | ||
77 | .map(|(stmt, rhs)| (edit.make_mut(stmt), rhs.clone_for_update())) | ||
78 | .collect(); | ||
79 | |||
80 | let tgt = edit.make_mut(tgt); | ||
81 | |||
82 | for (stmt, rhs) in assignments { | ||
83 | let mut stmt = stmt.syntax().clone(); | ||
84 | if let Some(parent) = stmt.parent() { | ||
85 | if ast::ExprStmt::cast(parent.clone()).is_some() { | ||
86 | stmt = parent.clone(); | ||
87 | } | ||
88 | } | ||
89 | ted::replace(stmt, rhs.syntax()); | ||
90 | } | ||
91 | let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone()); | ||
92 | let assign_stmt = make::expr_stmt(assign_expr); | ||
93 | |||
94 | ted::replace(tgt.syntax(), assign_stmt.syntax().clone_for_update()); | ||
68 | }, | 95 | }, |
69 | ) | 96 | ) |
70 | } | 97 | } |
71 | 98 | ||
72 | fn exprify_match( | 99 | struct AssignmentsCollector<'a> { |
73 | match_expr: &ast::MatchExpr, | 100 | sema: &'a hir::Semantics<'a, ide_db::RootDatabase>, |
74 | sema: &hir::Semantics<ide_db::RootDatabase>, | 101 | common_lhs: ast::Expr, |
75 | name: &ast::Expr, | 102 | assignments: Vec<(ast::BinExpr, ast::Expr)>, |
76 | ) -> Option<ast::Expr> { | 103 | } |
77 | let new_arm_list = match_expr | 104 | |
78 | .match_arm_list()? | 105 | impl<'a> AssignmentsCollector<'a> { |
79 | .arms() | 106 | fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> { |
80 | .map(|arm| { | 107 | for arm in match_expr.match_arm_list()?.arms() { |
81 | if let ast::Expr::BlockExpr(block) = arm.expr()? { | 108 | match arm.expr()? { |
82 | let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level()); | 109 | ast::Expr::BlockExpr(block) => self.collect_block(&block)?, |
83 | Some(arm.replace_descendant(block, new_block)) | 110 | ast::Expr::BinExpr(expr) => self.collect_expr(&expr)?, |
111 | _ => return None, | ||
112 | } | ||
113 | } | ||
114 | |||
115 | Some(()) | ||
116 | } | ||
117 | fn collect_if(&mut self, if_expr: &ast::IfExpr) -> Option<()> { | ||
118 | let then_branch = if_expr.then_branch()?; | ||
119 | self.collect_block(&then_branch)?; | ||
120 | |||
121 | match if_expr.else_branch()? { | ||
122 | ast::ElseBranch::Block(block) => self.collect_block(&block), | ||
123 | ast::ElseBranch::IfExpr(expr) => { | ||
124 | cov_mark::hit!(test_pull_assignment_up_chained_if); | ||
125 | self.collect_if(&expr) | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | fn collect_block(&mut self, block: &ast::BlockExpr) -> Option<()> { | ||
130 | let last_expr = block.tail_expr().or_else(|| { | ||
131 | if let ast::Stmt::ExprStmt(stmt) = block.statements().last()? { | ||
132 | stmt.expr() | ||
84 | } else { | 133 | } else { |
85 | None | 134 | None |
86 | } | 135 | } |
87 | }) | 136 | })?; |
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)) | ||
93 | } | ||
94 | 137 | ||
95 | fn exprify_if( | 138 | if let ast::Expr::BinExpr(expr) = last_expr { |
96 | statement: &ast::IfExpr, | 139 | return self.collect_expr(&expr); |
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 | )?) | ||
110 | } | 140 | } |
111 | }; | ||
112 | Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch))) | ||
113 | } | ||
114 | 141 | ||
115 | fn exprify_block( | 142 | None |
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 | } | 143 | } |
123 | 144 | ||
124 | let mut stmts: Vec<_> = block.statements().collect(); | 145 | fn collect_expr(&mut self, expr: &ast::BinExpr) -> Option<()> { |
125 | let stmt = stmts.pop()?; | 146 | if expr.op_kind()? == ast::BinOp::Assignment |
126 | 147 | && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs) | |
127 | if let ast::Stmt::ExprStmt(stmt) = stmt { | 148 | { |
128 | if let ast::Expr::BinExpr(expr) = stmt.expr()? { | 149 | self.assignments.push((expr.clone(), expr.rhs()?)); |
129 | if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name) | 150 | return Some(()); |
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()?))); | ||
133 | } | ||
134 | } | 151 | } |
152 | None | ||
135 | } | 153 | } |
136 | None | ||
137 | } | 154 | } |
138 | 155 | ||
139 | fn is_equivalent( | 156 | fn is_equivalent( |
@@ -243,6 +260,37 @@ fn foo() { | |||
243 | } | 260 | } |
244 | 261 | ||
245 | #[test] | 262 | #[test] |
263 | fn test_pull_assignment_up_assignment_expressions() { | ||
264 | check_assist( | ||
265 | pull_assignment_up, | ||
266 | r#" | ||
267 | fn foo() { | ||
268 | let mut a = 1; | ||
269 | |||
270 | match 1 { | ||
271 | 1 => { $0a = 2; }, | ||
272 | 2 => a = 3, | ||
273 | 3 => { | ||
274 | a = 4 | ||
275 | } | ||
276 | } | ||
277 | }"#, | ||
278 | r#" | ||
279 | fn foo() { | ||
280 | let mut a = 1; | ||
281 | |||
282 | a = match 1 { | ||
283 | 1 => { 2 }, | ||
284 | 2 => 3, | ||
285 | 3 => { | ||
286 | 4 | ||
287 | } | ||
288 | }; | ||
289 | }"#, | ||
290 | ); | ||
291 | } | ||
292 | |||
293 | #[test] | ||
246 | fn test_pull_assignment_up_not_last_not_applicable() { | 294 | fn test_pull_assignment_up_not_last_not_applicable() { |
247 | check_assist_not_applicable( | 295 | check_assist_not_applicable( |
248 | pull_assignment_up, | 296 | pull_assignment_up, |
@@ -439,4 +487,24 @@ fn foo() { | |||
439 | "#, | 487 | "#, |
440 | ) | 488 | ) |
441 | } | 489 | } |
490 | |||
491 | #[test] | ||
492 | fn test_cant_pull_non_assignments() { | ||
493 | cov_mark::check!(test_cant_pull_non_assignments); | ||
494 | check_assist_not_applicable( | ||
495 | pull_assignment_up, | ||
496 | r#" | ||
497 | fn foo() { | ||
498 | let mut a = 1; | ||
499 | let b = &mut a; | ||
500 | |||
501 | if true { | ||
502 | $0*b + 2; | ||
503 | } else { | ||
504 | *b + 3; | ||
505 | } | ||
506 | } | ||
507 | "#, | ||
508 | ) | ||
509 | } | ||
442 | } | 510 | } |