aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/pull_assignment_up.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/pull_assignment_up.rs')
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs212
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 @@
1use syntax::{ 1use syntax::{
2 ast::{self, edit::AstNodeEdit, make}, 2 ast::{self, make},
3 AstNode, 3 ted, AstNode,
4}; 4};
5 5
6use crate::{ 6use crate::{
@@ -37,103 +37,120 @@ use crate::{
37// ``` 37// ```
38pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 38pub(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
72fn exprify_match( 99struct 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()? 105impl<'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
95fn 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
115fn 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
139fn is_equivalent( 156fn 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#"
267fn 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#"
279fn 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#"
497fn 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}