diff options
Diffstat (limited to 'crates/ide_assists')
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::{ | |||
10 | use itertools::Itertools; | 10 | use itertools::Itertools; |
11 | use stdx::format_to; | 11 | use stdx::format_to; |
12 | use syntax::{ | 12 | use 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 | ||
1418 | fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) -> SyntaxNode { | 1418 | fn 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 | ||
1489 | fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> { | 1479 | fn 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 @@ | |||
1 | use syntax::{ | 1 | use syntax::{ |
2 | ast::{self, edit::AstNodeEdit, make}, | 2 | ast::{self, make}, |
3 | AstNode, | 3 | ted, AstNode, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{ | 6 | use crate::{ |
@@ -37,103 +37,101 @@ use crate::{ | |||
37 | // ``` | 37 | // ``` |
38 | pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 38 | pub(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 | ||
72 | fn exprify_match( | 87 | struct 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 | ||
95 | fn exprify_if( | 93 | impl<'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 | ||
115 | fn 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 | ||
139 | fn is_equivalent( | 137 | fn 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#" | ||
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 | |||
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#" | ||
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 | } | ||
489 | "#, | ||
490 | ) | ||
491 | } | ||
442 | } | 492 | } |