7 assist_context::{AssistContext, Assists},
11 // Assist: pull_assignment_up
13 // Extracts variable assignment to outside an if or match statement.
38 pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
41 let op_kind = assign_expr.op_kind()?;
42 if op_kind != (ast::BinaryOp::Assignment { op: None }) {
43 cov_mark::hit!(test_cant_pull_non_assignments);
47 let mut collector = AssignmentsCollector {
49 common_lhs: assign_expr.lhs()?,
50 assignments: Vec::new(),
53 let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
54 collector.collect_if(&if_expr)?;
56 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
57 collector.collect_match(&match_expr)?;
63 if let Some(parent) = tgt.syntax().parent() {
64 if matches!(parent.kind(), syntax::SyntaxKind::BIN_EXPR | syntax::SyntaxKind::LET_STMT) {
70 AssistId("pull_assignment_up", AssistKind::RefactorExtract),
72 tgt.syntax().text_range(),
74 let assignments: Vec<_> = collector
77 .map(|(stmt, rhs)| (edit.make_mut(stmt), rhs.clone_for_update()))
80 let tgt = edit.make_mut(tgt);
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();
89 ted::replace(stmt, rhs.syntax());
91 let assign_expr = make::expr_assignment(collector.common_lhs, tgt.clone());
92 let assign_stmt = make::expr_stmt(assign_expr);
94 ted::replace(tgt.syntax(), assign_stmt.syntax().clone_for_update());
99 struct AssignmentsCollector<'a> {
100 sema: &'a hir::Semantics<'a, ide_db::RootDatabase>,
101 common_lhs: ast::Expr,
102 assignments: Vec<(ast::BinExpr, ast::Expr)>,
105 impl<'a> AssignmentsCollector<'a> {
106 fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> {
107 for arm in match_expr.match_arm_list()?.arms() {
109 ast::Expr::BlockExpr(block) => self.collect_block(&block)?,
110 ast::Expr::BinExpr(expr) => self.collect_expr(&expr)?,
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)?;
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)
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()? {
138 if let ast::Expr::BinExpr(expr) = last_expr {
139 return self.collect_expr(&expr);
145 fn collect_expr(&mut self, expr: &ast::BinExpr) -> Option<()> {
146 if expr.op_kind()? == (ast::BinaryOp::Assignment { op: None })
147 && is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
149 self.assignments.push((expr.clone(), expr.rhs()?));
157 sema: &hir::Semantics<ide_db::RootDatabase>,
161 match (expr0, expr1) {
162 (ast::Expr::FieldExpr(field_expr0), ast::Expr::FieldExpr(field_expr1)) => {
163 cov_mark::hit!(test_pull_assignment_up_field_assignment);
164 sema.resolve_field(field_expr0) == sema.resolve_field(field_expr1)
166 (ast::Expr::PathExpr(path0), ast::Expr::PathExpr(path1)) => {
167 let path0 = path0.path();
168 let path1 = path1.path();
169 if let (Some(path0), Some(path1)) = (path0, path1) {
170 sema.resolve_path(&path0) == sema.resolve_path(&path1)
175 (ast::Expr::PrefixExpr(prefix0), ast::Expr::PrefixExpr(prefix1))
176 if prefix0.op_kind() == Some(ast::UnaryOp::Deref)
177 && prefix1.op_kind() == Some(ast::UnaryOp::Deref) =>
179 cov_mark::hit!(test_pull_assignment_up_deref);
180 if let (Some(prefix0), Some(prefix1)) = (prefix0.expr(), prefix1.expr()) {
181 is_equivalent(sema, &prefix0, &prefix1)
194 use crate::tests::{check_assist, check_assist_not_applicable};
197 fn test_pull_assignment_up_if() {
224 fn test_pull_assignment_up_match() {
263 fn test_pull_assignment_up_assignment_expressions() {
294 fn test_pull_assignment_up_not_last_not_applicable() {
295 check_assist_not_applicable(
312 fn test_pull_assignment_up_chained_if() {
313 cov_mark::check!(test_pull_assignment_up_chained_if);
344 fn test_pull_assignment_up_retains_stmts() {
375 fn pull_assignment_up_let_stmt_not_applicable() {
376 check_assist_not_applicable(
392 fn pull_assignment_up_if_missing_assigment_not_applicable() {
393 check_assist_not_applicable(
407 fn pull_assignment_up_match_missing_assigment_not_applicable() {
408 check_assist_not_applicable(
428 fn test_pull_assignment_up_field_assignment() {
429 cov_mark::check!(test_pull_assignment_up_field_assignment);
460 fn test_pull_assignment_up_deref() {
461 cov_mark::check!(test_pull_assignment_up_deref);
492 fn test_cant_pull_non_assignments() {
493 cov_mark::check!(test_cant_pull_non_assignments);
494 check_assist_not_applicable(