Skip to content

Commit 304cdd7

Browse files
bors[bot]matklad
andauthored
Merge #8770
8770: feat: add "mentoring instructions" test for pull up assist r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 96c5df9 + 1ee12b5 commit 304cdd7

File tree

2 files changed

+127
-74
lines changed

2 files changed

+127
-74
lines changed

crates/ide_assists/src/handlers/pull_assignment_up.rs

Lines changed: 124 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use syntax::{
2-
ast::{self, edit::AstNodeEdit, make},
3-
AstNode,
2+
ast::{self, make},
3+
ted, AstNode,
44
};
55

66
use crate::{
@@ -37,103 +37,101 @@ use crate::{
3737
// ```
3838
pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
3939
let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
40-
let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment {
41-
assign_expr.lhs()?
42-
} else {
40+
41+
let op_kind = assign_expr.op_kind()?;
42+
if op_kind != ast::BinOp::Assignment {
43+
cov_mark::hit!(test_cant_pull_non_assignments);
4344
return None;
45+
}
46+
47+
let mut collector = AssignmentsCollector {
48+
sema: &ctx.sema,
49+
common_lhs: assign_expr.lhs()?,
50+
assignments: Vec::new(),
4451
};
4552

46-
let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
47-
(
48-
ast::Expr::cast(if_expr.syntax().to_owned())?,
49-
exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()),
50-
)
53+
let tgt: ast::Expr = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
54+
collector.collect_if(&if_expr)?;
55+
if_expr.into()
5156
} else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
52-
(
53-
ast::Expr::cast(match_expr.syntax().to_owned())?,
54-
exprify_match(&match_expr, &ctx.sema, &name_expr)?,
55-
)
57+
collector.collect_match(&match_expr)?;
58+
match_expr.into()
5659
} else {
5760
return None;
5861
};
5962

60-
let expr_stmt = make::expr_stmt(new_stmt);
61-
6263
acc.add(
6364
AssistId("pull_assignment_up", AssistKind::RefactorExtract),
6465
"Pull assignment up",
65-
old_stmt.syntax().text_range(),
66+
tgt.syntax().text_range(),
6667
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());
6883
},
6984
)
7085
}
7186

72-
fn exprify_match(
73-
match_expr: &ast::MatchExpr,
74-
sema: &hir::Semantics<ide_db::RootDatabase>,
75-
name: &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))
87+
struct AssignmentsCollector<'a> {
88+
sema: &'a hir::Semantics<'a, ide_db::RootDatabase>,
89+
common_lhs: ast::Expr,
90+
assignments: Vec<(ast::ExprStmt, ast::Expr)>,
9391
}
9492

95-
fn exprify_if(
96-
statement: &ast::IfExpr,
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-
)?)
93+
impl<'a> AssignmentsCollector<'a> {
94+
fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> {
95+
for arm in match_expr.match_arm_list()?.arms() {
96+
match arm.expr()? {
97+
ast::Expr::BlockExpr(block) => self.collect_block(&block)?,
98+
_ => return None,
99+
}
110100
}
111-
};
112-
Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
113-
}
114101

115-
fn exprify_block(
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;
102+
Some(())
122103
}
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+
}
123120

124-
let mut stmts: Vec<_> = block.statements().collect();
125-
let stmt = stmts.pop()?;
126-
127-
if let ast::Stmt::ExprStmt(stmt) = stmt {
128-
if let ast::Expr::BinExpr(expr) = stmt.expr()? {
129-
if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name)
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()?)));
121+
let last_stmt = block.statements().last()?;
122+
if let ast::Stmt::ExprStmt(stmt) = last_stmt {
123+
if let ast::Expr::BinExpr(expr) = stmt.expr()? {
124+
if expr.op_kind()? == ast::BinOp::Assignment
125+
&& is_equivalent(self.sema, &expr.lhs()?, &self.common_lhs)
126+
{
127+
self.assignments.push((stmt, expr.rhs()?));
128+
return Some(());
129+
}
133130
}
134131
}
132+
133+
None
135134
}
136-
None
137135
}
138136

139137
fn is_equivalent(
@@ -242,6 +240,38 @@ fn foo() {
242240
);
243241
}
244242

243+
#[test]
244+
#[ignore]
245+
fn test_pull_assignment_up_assignment_expressions() {
246+
check_assist(
247+
pull_assignment_up,
248+
r#"
249+
fn 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#"
261+
fn 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+
245275
#[test]
246276
fn test_pull_assignment_up_not_last_not_applicable() {
247277
check_assist_not_applicable(
@@ -436,6 +466,26 @@ fn foo() {
436466
3
437467
};
438468
}
469+
"#,
470+
)
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#"
479+
fn 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+
}
439489
"#,
440490
)
441491
}

crates/syntax/src/ast/make.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@ pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr {
275275
let expr = elements.into_iter().format(", ");
276276
expr_from_text(&format!("({})", expr))
277277
}
278+
pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
279+
expr_from_text(&format!("{} = {}", lhs, rhs))
280+
}
278281
fn expr_from_text(text: &str) -> ast::Expr {
279282
ast_from_text(&format!("const C: () = {};", text))
280283
}

0 commit comments

Comments
 (0)