aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladyslav Katasonov <[email protected]>2021-02-05 01:35:41 +0000
committerVladyslav Katasonov <[email protected]>2021-02-05 01:35:41 +0000
commit876ca603166dcd2680652b42fb6bdd5358e59aa6 (patch)
tree074c5f4abd125ce2f8ac9d245090f636b6a7e7aa
parent271c1cb01325ac252b5153c3729462a4d96a0e0a (diff)
allow transitive `&mut` access for fields in extract_function
-rw-r--r--crates/assists/src/handlers/extract_function.rs119
1 files changed, 92 insertions, 27 deletions
diff --git a/crates/assists/src/handlers/extract_function.rs b/crates/assists/src/handlers/extract_function.rs
index 5d6f5bb26..49ea1c4b3 100644
--- a/crates/assists/src/handlers/extract_function.rs
+++ b/crates/assists/src/handlers/extract_function.rs
@@ -503,17 +503,42 @@ fn reference_is_exclusive(
503 } 503 }
504 504
505 // we take `&mut` reference to variable: `&mut v` 505 // we take `&mut` reference to variable: `&mut v`
506 let path = path_element_of_reference(body, reference); 506 let path = match path_element_of_reference(body, reference) {
507 if is_mut_ref_expr(path.as_ref()).unwrap_or(false) { 507 Some(path) => path,
508 return true; 508 None => return false,
509 };
510
511 expr_require_exclusive_access(ctx, &path).unwrap_or(false)
512}
513
514/// checks if this expr requires `&mut` access, recurses on field access
515fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> {
516 let parent = expr.syntax().parent()?;
517
518 if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) {
519 if bin_expr.op_kind()?.is_assignment() {
520 return Some(bin_expr.lhs()?.syntax() == expr.syntax());
521 }
522 return Some(false);
509 } 523 }
510 524
511 // we call method with `&mut self` receiver 525 if let Some(ref_expr) = ast::RefExpr::cast(parent.clone()) {
512 if is_mut_method_call_receiver(ctx, path.as_ref()).unwrap_or(false) { 526 return Some(ref_expr.mut_token().is_some());
513 return true; 527 }
528
529 if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
530 let func = ctx.sema.resolve_method_call(&method_call)?;
531 let self_param = func.self_param(ctx.db())?;
532 let access = self_param.access(ctx.db());
533
534 return Some(matches!(access, hir::Access::Exclusive));
535 }
536
537 if let Some(field) = ast::FieldExpr::cast(parent) {
538 return expr_require_exclusive_access(ctx, &field.into());
514 } 539 }
515 540
516 false 541 Some(false)
517} 542}
518 543
519/// Container of local varaible usages 544/// Container of local varaible usages
@@ -567,24 +592,6 @@ fn path_element_of_reference(
567 Some(path) 592 Some(path)
568} 593}
569 594
570fn is_mut_ref_expr(path: Option<&ast::Expr>) -> Option<bool> {
571 let path = path?;
572 let ref_expr = path.syntax().parent().and_then(ast::RefExpr::cast)?;
573 Some(ref_expr.mut_token().is_some())
574}
575
576/// checks if `path` is the receiver in method call that requires `&mut self` access
577fn is_mut_method_call_receiver(ctx: &AssistContext, path: Option<&ast::Expr>) -> Option<bool> {
578 let path = path?;
579 let method_call = path.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
580
581 let func = ctx.sema.resolve_method_call(&method_call)?;
582 let self_param = func.self_param(ctx.db())?;
583 let access = self_param.access(ctx.db());
584
585 Some(matches!(access, hir::Access::Exclusive))
586}
587
588/// list local variables defined inside `body` 595/// list local variables defined inside `body`
589fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> { 596fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> {
590 // FIXME: this doesn't work well with macros 597 // FIXME: this doesn't work well with macros
@@ -872,7 +879,7 @@ fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode)
872 .filter_map(|reference| path_element_of_reference(syntax, reference)); 879 .filter_map(|reference| path_element_of_reference(syntax, reference));
873 for path in usages { 880 for path in usages {
874 match path.syntax().ancestors().skip(1).find_map(ast::Expr::cast) { 881 match path.syntax().ancestors().skip(1).find_map(ast::Expr::cast) {
875 Some(ast::Expr::MethodCallExpr(_)) => { 882 Some(ast::Expr::MethodCallExpr(_)) | Some(ast::Expr::FieldExpr(_)) => {
876 // do nothing 883 // do nothing
877 } 884 }
878 Some(ast::Expr::RefExpr(node)) 885 Some(ast::Expr::RefExpr(node))
@@ -1673,6 +1680,64 @@ fn $0fun_name(n: &mut i32) {
1673 } 1680 }
1674 1681
1675 #[test] 1682 #[test]
1683 fn mut_field_from_outer_scope() {
1684 check_assist(
1685 extract_function,
1686 r"
1687struct C { n: i32 }
1688fn foo() {
1689 let mut c = C { n: 0 };
1690 $0c.n += 1;$0
1691 let m = c.n + 1;
1692}",
1693 r"
1694struct C { n: i32 }
1695fn foo() {
1696 let mut c = C { n: 0 };
1697 fun_name(&mut c);
1698 let m = c.n + 1;
1699}
1700
1701fn $0fun_name(c: &mut C) {
1702 c.n += 1;
1703}",
1704 );
1705 }
1706
1707 #[test]
1708 fn mut_nested_field_from_outer_scope() {
1709 check_assist(
1710 extract_function,
1711 r"
1712struct P { n: i32}
1713struct C { p: P }
1714fn foo() {
1715 let mut c = C { p: P { n: 0 } };
1716 let mut v = C { p: P { n: 0 } };
1717 let u = C { p: P { n: 0 } };
1718 $0c.p.n += u.p.n;
1719 let r = &mut v.p.n;$0
1720 let m = c.p.n + v.p.n + u.p.n;
1721}",
1722 r"
1723struct P { n: i32}
1724struct C { p: P }
1725fn foo() {
1726 let mut c = C { p: P { n: 0 } };
1727 let mut v = C { p: P { n: 0 } };
1728 let u = C { p: P { n: 0 } };
1729 fun_name(&mut c, &u, &mut v);
1730 let m = c.p.n + v.p.n + u.p.n;
1731}
1732
1733fn $0fun_name(c: &mut C, u: &C, v: &mut C) {
1734 c.p.n += u.p.n;
1735 let r = &mut v.p.n;
1736}",
1737 );
1738 }
1739
1740 #[test]
1676 fn mut_param_many_usages_stmt() { 1741 fn mut_param_many_usages_stmt() {
1677 check_assist( 1742 check_assist(
1678 extract_function, 1743 extract_function,
@@ -1999,7 +2064,7 @@ fn foo() {
1999} 2064}
2000 2065
2001fn $0fun_name(c: &Counter) { 2066fn $0fun_name(c: &Counter) {
2002 let n = *c.0; 2067 let n = c.0;
2003}", 2068}",
2004 ); 2069 );
2005 } 2070 }