aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs198
-rw-r--r--crates/syntax/src/ast/edit.rs12
-rw-r--r--crates/syntax/src/ast/make.rs3
3 files changed, 128 insertions, 85 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 @@
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,101 @@ 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);
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
72fn exprify_match( 87struct 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
95fn exprify_if( 93impl<'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
115fn 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
139fn is_equivalent( 137fn 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#"
249fn 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#"
261fn 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#"
479fn 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/edit.rs b/crates/syntax/src/ast/edit.rs
index 8c60927e4..cbc75f922 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -665,18 +665,8 @@ pub trait AstNodeEdit: AstNode + Clone + Sized {
665 665
666 #[must_use] 666 #[must_use]
667 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self { 667 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
668 self.replace_descendants(iter::once((old, new)))
669 }
670
671 #[must_use]
672 fn replace_descendants<D: AstNode>(
673 &self,
674 replacement_map: impl IntoIterator<Item = (D, D)>,
675 ) -> Self {
676 let mut rewriter = SyntaxRewriter::default(); 668 let mut rewriter = SyntaxRewriter::default();
677 for (from, to) in replacement_map { 669 rewriter.replace(old.syntax(), new.syntax());
678 rewriter.replace(from.syntax(), to.syntax())
679 }
680 rewriter.rewrite_ast(self) 670 rewriter.rewrite_ast(self)
681 } 671 }
682 fn indent_level(&self) -> IndentLevel { 672 fn indent_level(&self) -> IndentLevel {
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}
278pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
279 expr_from_text(&format!("{} = {}", lhs, rhs))
280}
278fn expr_from_text(text: &str) -> ast::Expr { 281fn 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}