aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/extract_assignment.rs103
1 files changed, 89 insertions, 14 deletions
diff --git a/crates/assists/src/handlers/extract_assignment.rs b/crates/assists/src/handlers/extract_assignment.rs
index 281cf5d24..ae99598c0 100644
--- a/crates/assists/src/handlers/extract_assignment.rs
+++ b/crates/assists/src/handlers/extract_assignment.rs
@@ -1,4 +1,3 @@
1use hir::AsName;
2use syntax::{ 1use syntax::{
3 ast::{self, edit::AstNodeEdit, make}, 2 ast::{self, edit::AstNodeEdit, make},
4 AstNode, 3 AstNode,
@@ -38,15 +37,23 @@ use crate::{
38// } 37// }
39// ``` 38// ```
40pub(crate) fn extract_assigment(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 39pub(crate) fn extract_assigment(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41 let name = ctx.find_node_at_offset::<ast::NameRef>()?.as_name(); 40 let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
41 let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment {
42 assign_expr.lhs()?
43 } else {
44 return None;
45 };
42 46
43 let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { 47 let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
44 ( 48 (
45 ast::Expr::cast(if_expr.syntax().to_owned())?, 49 ast::Expr::cast(if_expr.syntax().to_owned())?,
46 exprify_if(&if_expr, &name)?.indent(if_expr.indent_level()), 50 exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()),
47 ) 51 )
48 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() { 52 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
49 (ast::Expr::cast(match_expr.syntax().to_owned())?, exprify_match(&match_expr, &name)?) 53 (
54 ast::Expr::cast(match_expr.syntax().to_owned())?,
55 exprify_match(&match_expr, &ctx.sema, &name_expr)?,
56 )
50 } else { 57 } else {
51 return None; 58 return None;
52 }; 59 };
@@ -58,18 +65,22 @@ pub(crate) fn extract_assigment(acc: &mut Assists, ctx: &AssistContext) -> Optio
58 "Extract assignment", 65 "Extract assignment",
59 old_stmt.syntax().text_range(), 66 old_stmt.syntax().text_range(),
60 move |edit| { 67 move |edit| {
61 edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name, expr_stmt)); 68 edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt));
62 }, 69 },
63 ) 70 )
64} 71}
65 72
66fn exprify_match(match_expr: &ast::MatchExpr, name: &hir::Name) -> Option<ast::Expr> { 73fn exprify_match(
74 match_expr: &ast::MatchExpr,
75 sema: &hir::Semantics<ide_db::RootDatabase>,
76 name: &ast::Expr,
77) -> Option<ast::Expr> {
67 let new_arm_list = match_expr 78 let new_arm_list = match_expr
68 .match_arm_list()? 79 .match_arm_list()?
69 .arms() 80 .arms()
70 .map(|arm| { 81 .map(|arm| {
71 if let ast::Expr::BlockExpr(block) = arm.expr()? { 82 if let ast::Expr::BlockExpr(block) = arm.expr()? {
72 let new_block = exprify_block(&block, name)?.indent(block.indent_level()); 83 let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level());
73 Some(arm.replace_descendant(block, new_block)) 84 Some(arm.replace_descendant(block, new_block))
74 } else { 85 } else {
75 None 86 None
@@ -82,21 +93,31 @@ fn exprify_match(match_expr: &ast::MatchExpr, name: &hir::Name) -> Option<ast::E
82 Some(make::expr_match(match_expr.expr()?, new_arm_list)) 93 Some(make::expr_match(match_expr.expr()?, new_arm_list))
83} 94}
84 95
85fn exprify_if(statement: &ast::IfExpr, name: &hir::Name) -> Option<ast::Expr> { 96fn exprify_if(
86 let then_branch = exprify_block(&statement.then_branch()?, name)?; 97 statement: &ast::IfExpr,
98 sema: &hir::Semantics<ide_db::RootDatabase>,
99 name: &ast::Expr,
100) -> Option<ast::Expr> {
101 let then_branch = exprify_block(&statement.then_branch()?, sema, name)?;
87 let else_branch = match statement.else_branch()? { 102 let else_branch = match statement.else_branch()? {
88 ast::ElseBranch::Block(ref block) => ast::ElseBranch::Block(exprify_block(block, name)?), 103 ast::ElseBranch::Block(ref block) => {
104 ast::ElseBranch::Block(exprify_block(block, sema, name)?)
105 }
89 ast::ElseBranch::IfExpr(expr) => { 106 ast::ElseBranch::IfExpr(expr) => {
90 mark::hit!(test_extract_assigment_chained_if); 107 mark::hit!(test_extract_assigment_chained_if);
91 ast::ElseBranch::IfExpr(ast::IfExpr::cast( 108 ast::ElseBranch::IfExpr(ast::IfExpr::cast(
92 exprify_if(&expr, name)?.syntax().to_owned(), 109 exprify_if(&expr, sema, name)?.syntax().to_owned(),
93 )?) 110 )?)
94 } 111 }
95 }; 112 };
96 Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch))) 113 Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
97} 114}
98 115
99fn exprify_block(block: &ast::BlockExpr, name: &hir::Name) -> Option<ast::BlockExpr> { 116fn exprify_block(
117 block: &ast::BlockExpr,
118 sema: &hir::Semantics<ide_db::RootDatabase>,
119 name: &ast::Expr,
120) -> Option<ast::BlockExpr> {
100 if block.expr().is_some() { 121 if block.expr().is_some() {
101 return None; 122 return None;
102 } 123 }
@@ -106,8 +127,7 @@ fn exprify_block(block: &ast::BlockExpr, name: &hir::Name) -> Option<ast::BlockE
106 127
107 if let ast::Stmt::ExprStmt(stmt) = stmt { 128 if let ast::Stmt::ExprStmt(stmt) = stmt {
108 if let ast::Expr::BinExpr(expr) = stmt.expr()? { 129 if let ast::Expr::BinExpr(expr) = stmt.expr()? {
109 if expr.op_kind()? == ast::BinOp::Assignment 130 if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name)
110 && &expr.lhs()?.name_ref()?.as_name() == name
111 { 131 {
112 // The last statement in the block is an assignment to the name we want 132 // The last statement in the block is an assignment to the name we want
113 return Some(make::block_expr(stmts, Some(expr.rhs()?))); 133 return Some(make::block_expr(stmts, Some(expr.rhs()?)));
@@ -117,6 +137,29 @@ fn exprify_block(block: &ast::BlockExpr, name: &hir::Name) -> Option<ast::BlockE
117 None 137 None
118} 138}
119 139
140fn is_equivalent(
141 sema: &hir::Semantics<ide_db::RootDatabase>,
142 expr0: &ast::Expr,
143 expr1: &ast::Expr,
144) -> bool {
145 match (expr0, expr1) {
146 (ast::Expr::FieldExpr(field_expr0), ast::Expr::FieldExpr(field_expr1)) => {
147 mark::hit!(test_extract_assignment_field_assignment);
148 sema.resolve_field(field_expr0) == sema.resolve_field(field_expr1)
149 }
150 (ast::Expr::PathExpr(path0), ast::Expr::PathExpr(path1)) => {
151 let path0 = path0.path();
152 let path1 = path1.path();
153 if let (Some(path0), Some(path1)) = (path0, path1) {
154 sema.resolve_path(&path0) == sema.resolve_path(&path1)
155 } else {
156 false
157 }
158 }
159 _ => false,
160 }
161}
162
120#[cfg(test)] 163#[cfg(test)]
121mod tests { 164mod tests {
122 use super::*; 165 use super::*;
@@ -322,4 +365,36 @@ fn foo() {
322}"#, 365}"#,
323 ) 366 )
324 } 367 }
368
369 #[test]
370 fn test_extract_assignment_field_assignment() {
371 mark::check!(test_extract_assignment_field_assignment);
372 check_assist(
373 extract_assigment,
374 r#"
375struct A(usize);
376
377fn foo() {
378 let mut a = A(1);
379
380 if true {
381 <|>a.0 = 2;
382 } else {
383 a.0 = 3;
384 }
385}"#,
386 r#"
387struct A(usize);
388
389fn foo() {
390 let mut a = A(1);
391
392 a.0 = if true {
393 2
394 } else {
395 3
396 };
397}"#,
398 )
399 }
325} 400}