diff options
author | Aleksey Kladov <[email protected]> | 2021-05-08 21:09:36 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-05-08 21:11:42 +0100 |
commit | 1755b57e1a568176bc0bda144889460e7d603cd5 (patch) | |
tree | 3f6805d7bfe6824c2ce10e4922d5b6e1b2c13347 /crates/ide_assists | |
parent | e603090961d950b1130950c179361b530c7ad10a (diff) |
internal: pull_assignment_up uses mutable trees
Diffstat (limited to 'crates/ide_assists')
-rw-r--r-- | crates/ide_assists/src/handlers/pull_assignment_up.rs | 137 |
1 files changed, 68 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 @@ | |||
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::{ |
@@ -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 | ||
74 | fn exprify_match( | 87 | struct 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 | ||
97 | fn exprify_if( | 93 | impl<'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 | ||
115 | fn 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 | ||
139 | fn is_equivalent( | 138 | fn is_equivalent( |