aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs2
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs102
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs198
3 files changed, 171 insertions, 131 deletions
diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index b5b5ada5e..70949ca35 100644
--- a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -107,7 +107,7 @@ fn edit_struct_references(
107 names: &[ast::Name], 107 names: &[ast::Name],
108) { 108) {
109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt))); 109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt)));
110 let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all(); 110 let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
111 111
112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { 112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
113 match_ast! { 113 match_ast! {
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index 93b28370c..4116985ae 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -10,7 +10,6 @@ use ide_db::{
10use itertools::Itertools; 10use itertools::Itertools;
11use stdx::format_to; 11use stdx::format_to;
12use syntax::{ 12use syntax::{
13 algo::SyntaxRewriter,
14 ast::{ 13 ast::{
15 self, 14 self,
16 edit::{AstNodeEdit, IndentLevel}, 15 edit::{AstNodeEdit, IndentLevel},
@@ -1362,7 +1361,8 @@ fn rewrite_body_segment(
1362 syntax: &SyntaxNode, 1361 syntax: &SyntaxNode,
1363) -> SyntaxNode { 1362) -> SyntaxNode {
1364 let syntax = fix_param_usages(ctx, params, syntax); 1363 let syntax = fix_param_usages(ctx, params, syntax);
1365 update_external_control_flow(handler, &syntax) 1364 update_external_control_flow(handler, &syntax);
1365 syntax
1366} 1366}
1367 1367
1368/// change all usages to account for added `&`/`&mut` for some params 1368/// change all usages to account for added `&`/`&mut` for some params
@@ -1415,75 +1415,65 @@ fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode)
1415 res 1415 res
1416} 1416}
1417 1417
1418fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) -> SyntaxNode { 1418fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) {
1419 let mut rewriter = SyntaxRewriter::default();
1420
1421 let mut nested_loop = None; 1419 let mut nested_loop = None;
1422 let mut nested_scope = None; 1420 let mut nested_scope = None;
1423 for event in syntax.preorder() { 1421 for event in syntax.preorder() {
1424 let node = match event { 1422 match event {
1425 WalkEvent::Enter(e) => { 1423 WalkEvent::Enter(e) => match e.kind() {
1426 match e.kind() { 1424 SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => {
1427 SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => { 1425 if nested_loop.is_none() {
1428 if nested_loop.is_none() { 1426 nested_loop = Some(e.clone());
1429 nested_loop = Some(e.clone());
1430 }
1431 } 1427 }
1432 SyntaxKind::FN 1428 }
1433 | SyntaxKind::CONST 1429 SyntaxKind::FN
1434 | SyntaxKind::STATIC 1430 | SyntaxKind::CONST
1435 | SyntaxKind::IMPL 1431 | SyntaxKind::STATIC
1436 | SyntaxKind::MODULE => { 1432 | SyntaxKind::IMPL
1437 if nested_scope.is_none() { 1433 | SyntaxKind::MODULE => {
1438 nested_scope = Some(e.clone()); 1434 if nested_scope.is_none() {
1439 } 1435 nested_scope = Some(e.clone());
1440 } 1436 }
1441 _ => {}
1442 } 1437 }
1443 e 1438 _ => {}
1444 } 1439 },
1445 WalkEvent::Leave(e) => { 1440 WalkEvent::Leave(e) => {
1441 if nested_scope.is_none() {
1442 if let Some(expr) = ast::Expr::cast(e.clone()) {
1443 match expr {
1444 ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => {
1445 let expr = return_expr.expr();
1446 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1447 ted::replace(return_expr.syntax(), replacement.syntax())
1448 }
1449 }
1450 ast::Expr::BreakExpr(break_expr) if nested_loop.is_none() => {
1451 let expr = break_expr.expr();
1452 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1453 ted::replace(break_expr.syntax(), replacement.syntax())
1454 }
1455 }
1456 ast::Expr::ContinueExpr(continue_expr) if nested_loop.is_none() => {
1457 if let Some(replacement) = make_rewritten_flow(handler, None) {
1458 ted::replace(continue_expr.syntax(), replacement.syntax())
1459 }
1460 }
1461 _ => {
1462 // do nothing
1463 }
1464 }
1465 }
1466 }
1467
1446 if nested_loop.as_ref() == Some(&e) { 1468 if nested_loop.as_ref() == Some(&e) {
1447 nested_loop = None; 1469 nested_loop = None;
1448 } 1470 }
1449 if nested_scope.as_ref() == Some(&e) { 1471 if nested_scope.as_ref() == Some(&e) {
1450 nested_scope = None; 1472 nested_scope = None;
1451 } 1473 }
1452 continue;
1453 } 1474 }
1454 }; 1475 };
1455 if nested_scope.is_some() {
1456 continue;
1457 }
1458 let expr = match ast::Expr::cast(node) {
1459 Some(e) => e,
1460 None => continue,
1461 };
1462 match expr {
1463 ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => {
1464 let expr = return_expr.expr();
1465 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1466 rewriter.replace_ast(&return_expr.into(), &replacement);
1467 }
1468 }
1469 ast::Expr::BreakExpr(break_expr) if nested_loop.is_none() => {
1470 let expr = break_expr.expr();
1471 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1472 rewriter.replace_ast(&break_expr.into(), &replacement);
1473 }
1474 }
1475 ast::Expr::ContinueExpr(continue_expr) if nested_loop.is_none() => {
1476 if let Some(replacement) = make_rewritten_flow(handler, None) {
1477 rewriter.replace_ast(&continue_expr.into(), &replacement);
1478 }
1479 }
1480 _ => {
1481 // do nothing
1482 }
1483 }
1484 } 1476 }
1485
1486 rewriter.rewrite(syntax)
1487} 1477}
1488 1478
1489fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> { 1479fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> {
@@ -1502,7 +1492,7 @@ fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Op
1502 make::expr_call(make::expr_path(make_path_from_text("Err")), args) 1492 make::expr_call(make::expr_path(make_path_from_text("Err")), args)
1503 } 1493 }
1504 }; 1494 };
1505 Some(make::expr_return(Some(value))) 1495 Some(make::expr_return(Some(value)).clone_for_update())
1506} 1496}
1507 1497
1508#[cfg(test)] 1498#[cfg(test)]
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}