diff options
-rw-r--r-- | crates/assists/src/handlers/extract_function.rs | 110 |
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; | |||
2 | use hir::{HirDisplay, Local}; | 2 | use hir::{HirDisplay, Local}; |
3 | use ide_db::{ | 3 | use ide_db::{ |
4 | defs::{Definition, NameRefClass}, | 4 | defs::{Definition, NameRefClass}, |
5 | search::{ReferenceAccess, SearchScope}, | 5 | search::{FileReference, ReferenceAccess, SearchScope}, |
6 | }; | 6 | }; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use stdx::format_to; | 8 | use 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 | }; |
20 | use test_utils::mark; | 20 | use 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 | ||
419 | fn 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 | |||
426 | fn 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 | |||
408 | fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode { | 432 | fn 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" | ||
1471 | fn foo() { | ||
1472 | let mut n = 1; | ||
1473 | $0let v = &mut n; | ||
1474 | *v += 1;$0 | ||
1475 | let k = n; | ||
1476 | }", | ||
1477 | r" | ||
1478 | fn foo() { | ||
1479 | let mut n = 1; | ||
1480 | fun_name(&mut n); | ||
1481 | let k = n; | ||
1482 | } | ||
1483 | |||
1484 | fn $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" | ||
1496 | fn foo() { | ||
1497 | let mut n = 1; | ||
1498 | $0let v = &mut n; | ||
1499 | *v += 1;$0 | ||
1500 | }", | ||
1501 | r" | ||
1502 | fn foo() { | ||
1503 | let mut n = 1; | ||
1504 | fun_name(n); | ||
1505 | } | ||
1506 | |||
1507 | fn $0fun_name(mut n: i32) { | ||
1508 | let v = &mut n; | ||
1509 | *v += 1; | ||
1510 | }", | ||
1511 | ); | ||
1512 | } | ||
1409 | } | 1513 | } |