diff options
-rw-r--r-- | crates/ide_assists/src/handlers/pull_assignment_up.rs | 198 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 3 |
2 files changed, 127 insertions, 74 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..28d14b9c3 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,101 @@ 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); | ||
61 | |||
62 | acc.add( | 63 | acc.add( |
63 | AssistId("pull_assignment_up", AssistKind::RefactorExtract), | 64 | AssistId("pull_assignment_up", AssistKind::RefactorExtract), |
64 | "Pull assignment up", | 65 | "Pull assignment up", |
65 | old_stmt.syntax().text_range(), | 66 | tgt.syntax().text_range(), |
66 | move |edit| { | 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( | 87 | struct AssignmentsCollector<'a> { |
73 | match_expr: &ast::MatchExpr, | 88 | sema: &'a hir::Semantics<'a, ide_db::RootDatabase>, |
74 | sema: &hir::Semantics<ide_db::RootDatabase>, | 89 | common_lhs: ast::Expr, |
75 | name: &ast::Expr, | 90 | assignments: Vec<(ast::ExprStmt, 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)) | ||
93 | } | 91 | } |
94 | 92 | ||
95 | fn exprify_if( | 93 | impl<'a> AssignmentsCollector<'a> { |
96 | statement: &ast::IfExpr, | 94 | fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> { |
97 | sema: &hir::Semantics<ide_db::RootDatabase>, | 95 | for arm in match_expr.match_arm_list()?.arms() { |
98 | name: &ast::Expr, | 96 | match arm.expr()? { |
99 | ) -> Option<ast::Expr> { | 97 | ast::Expr::BlockExpr(block) => self.collect_block(&block)?, |
100 | let then_branch = exprify_block(&statement.then_branch()?, sema, name)?; | 98 | _ => return None, |
101 | let else_branch = match statement.else_branch()? { | 99 | } |
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 | } | 100 | } |
111 | }; | ||
112 | Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch))) | ||
113 | } | ||
114 | 101 | ||
115 | fn exprify_block( | 102 | 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 | } | 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(); | 121 | let last_stmt = block.statements().last()?; |
125 | let stmt = stmts.pop()?; | 122 | if let ast::Stmt::ExprStmt(stmt) = last_stmt { |
126 | 123 | if let ast::Expr::BinExpr(expr) = stmt.expr()? { | |
127 | if let ast::Stmt::ExprStmt(stmt) = stmt { | 124 | if expr.op_kind()? == ast::BinOp::Assignment |
128 | if let ast::Expr::BinExpr(expr) = stmt.expr()? { | 125 | && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs) |
129 | if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name) | 126 | { |
130 | { | 127 | self.assignments.push((stmt, expr.rhs()?)); |
131 | // The last statement in the block is an assignment to the name we want | 128 | return Some(()); |
132 | return Some(make::block_expr(stmts, Some(expr.rhs()?))); | 129 | } |
133 | } | 130 | } |
134 | } | 131 | } |
132 | |||
133 | None | ||
135 | } | 134 | } |
136 | None | ||
137 | } | 135 | } |
138 | 136 | ||
139 | fn is_equivalent( | 137 | fn is_equivalent( |
@@ -243,6 +241,38 @@ fn foo() { | |||
243 | } | 241 | } |
244 | 242 | ||
245 | #[test] | 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 | |||
275 | #[test] | ||
246 | fn test_pull_assignment_up_not_last_not_applicable() { | 276 | fn test_pull_assignment_up_not_last_not_applicable() { |
247 | check_assist_not_applicable( | 277 | check_assist_not_applicable( |
248 | pull_assignment_up, | 278 | pull_assignment_up, |
@@ -439,4 +469,24 @@ fn foo() { | |||
439 | "#, | 469 | "#, |
440 | ) | 470 | ) |
441 | } | 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 | } | ||
489 | "#, | ||
490 | ) | ||
491 | } | ||
442 | } | 492 | } |
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 | } |
278 | pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { | ||
279 | expr_from_text(&format!("{} = {}", lhs, rhs)) | ||
280 | } | ||
278 | fn expr_from_text(text: &str) -> ast::Expr { | 281 | fn 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 | } |