aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/extract_function.rs110
1 files changed, 107 insertions, 3 deletions
diff --git a/crates/assists/src/handlers/extract_function.rs b/crates/assists/src/handlers/extract_function.rs
index ffa8bd77d..a4b23d756 100644
--- a/crates/assists/src/handlers/extract_function.rs
+++ b/crates/assists/src/handlers/extract_function.rs
@@ -2,7 +2,7 @@ use either::Either;
2use hir::{HirDisplay, Local}; 2use hir::{HirDisplay, Local};
3use ide_db::{ 3use ide_db::{
4 defs::{Definition, NameRefClass}, 4 defs::{Definition, NameRefClass},
5 search::{ReferenceAccess, SearchScope}, 5 search::{FileReference, ReferenceAccess, SearchScope},
6}; 6};
7use itertools::Itertools; 7use itertools::Itertools;
8use stdx::format_to; 8use stdx::format_to;
@@ -15,7 +15,7 @@ use syntax::{
15 }, 15 },
16 Direction, SyntaxElement, 16 Direction, SyntaxElement,
17 SyntaxKind::{self, BLOCK_EXPR, BREAK_EXPR, COMMENT, PATH_EXPR, RETURN_EXPR}, 17 SyntaxKind::{self, BLOCK_EXPR, BREAK_EXPR, COMMENT, PATH_EXPR, RETURN_EXPR},
18 SyntaxNode, TextRange, T, 18 SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, T,
19}; 19};
20use test_utils::mark; 20use test_utils::mark;
21 21
@@ -140,7 +140,18 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
140 .iter() 140 .iter()
141 .flat_map(|(_, rs)| rs.iter()) 141 .flat_map(|(_, rs)| rs.iter())
142 .filter(|reference| body.contains_range(reference.range)) 142 .filter(|reference| body.contains_range(reference.range))
143 .any(|reference| reference.access == Some(ReferenceAccess::Write)); 143 .any(|reference| {
144 if reference.access == Some(ReferenceAccess::Write) {
145 return true;
146 }
147
148 let path = path_at_offset(&body, reference);
149 if is_mut_ref_expr(path.as_ref()).unwrap_or(false) {
150 return true;
151 }
152
153 false
154 });
144 155
145 Param { node, has_usages_afterwards, has_mut_inside_body, is_copy: true } 156 Param { node, has_usages_afterwards, has_mut_inside_body, is_copy: true }
146 }) 157 })
@@ -405,6 +416,19 @@ fn format_type(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> Stri
405 ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string()) 416 ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string())
406} 417}
407 418
419fn path_at_offset(body: &FunctionBody, reference: &FileReference) -> Option<ast::Expr> {
420 let var = body.token_at_offset(reference.range.start()).right_biased()?;
421 let path = var.ancestors().find_map(ast::Expr::cast)?;
422 stdx::always!(matches!(path, ast::Expr::PathExpr(_)));
423 Some(path)
424}
425
426fn is_mut_ref_expr(path: Option<&ast::Expr>) -> Option<bool> {
427 let path = path?;
428 let ref_expr = path.syntax().parent().and_then(ast::RefExpr::cast)?;
429 Some(ref_expr.mut_token().is_some())
430}
431
408fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { 432fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode {
409 let mut rewriter = SyntaxRewriter::default(); 433 let mut rewriter = SyntaxRewriter::default();
410 for param in params { 434 for param in params {
@@ -551,6 +575,38 @@ impl FunctionBody {
551 } 575 }
552 } 576 }
553 577
578 fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> {
579 match self {
580 FunctionBody::Expr(expr) => expr.syntax().token_at_offset(offset),
581 FunctionBody::Span { elements, .. } => {
582 stdx::always!(self.text_range().contains(offset));
583 let mut iter = elements
584 .iter()
585 .filter(|element| element.text_range().contains_inclusive(offset));
586 let element1 = iter.next().expect("offset does not fall into body");
587 let element2 = iter.next();
588 stdx::always!(iter.next().is_none(), "> 2 tokens at offset");
589 let t1 = match element1 {
590 syntax::NodeOrToken::Node(node) => node.token_at_offset(offset),
591 syntax::NodeOrToken::Token(token) => TokenAtOffset::Single(token.clone()),
592 };
593 let t2 = element2.map(|e| match e {
594 syntax::NodeOrToken::Node(node) => node.token_at_offset(offset),
595 syntax::NodeOrToken::Token(token) => TokenAtOffset::Single(token.clone()),
596 });
597
598 match t2 {
599 Some(t2) => match (t1.clone().right_biased(), t2.clone().left_biased()) {
600 (Some(e1), Some(e2)) => TokenAtOffset::Between(e1, e2),
601 (Some(_), None) => t1,
602 (None, _) => t2,
603 },
604 None => t1,
605 }
606 }
607 }
608 }
609
554 fn text_range(&self) -> TextRange { 610 fn text_range(&self) -> TextRange {
555 match self { 611 match self {
556 FunctionBody::Expr(expr) => expr.syntax().text_range(), 612 FunctionBody::Expr(expr) => expr.syntax().text_range(),
@@ -1406,4 +1462,52 @@ fn $0fun_name(mut n: i32) {
1406}", 1462}",
1407 ); 1463 );
1408 } 1464 }
1465
1466 #[test]
1467 fn mut_param_because_of_mut_ref() {
1468 check_assist(
1469 extract_function,
1470 r"
1471fn foo() {
1472 let mut n = 1;
1473 $0let v = &mut n;
1474 *v += 1;$0
1475 let k = n;
1476}",
1477 r"
1478fn foo() {
1479 let mut n = 1;
1480 fun_name(&mut n);
1481 let k = n;
1482}
1483
1484fn $0fun_name(n: &mut i32) {
1485 let v = n;
1486 *v += 1;
1487}",
1488 );
1489 }
1490
1491 #[test]
1492 fn mut_param_by_value_because_of_mut_ref() {
1493 check_assist(
1494 extract_function,
1495 r"
1496fn foo() {
1497 let mut n = 1;
1498 $0let v = &mut n;
1499 *v += 1;$0
1500}",
1501 r"
1502fn foo() {
1503 let mut n = 1;
1504 fun_name(n);
1505}
1506
1507fn $0fun_name(mut n: i32) {
1508 let v = &mut n;
1509 *v += 1;
1510}",
1511 );
1512 }
1409} 1513}