Skip to content

Commit cd377d9

Browse files
committed
Auto merge of rust-lang#17991 - ChayimFriedman2:extract-variable-ref, r=Veykril
fix: Don't add reference when it isn't needed for the "Extract variable" assist I.e. don't generate `let var_name = &foo()`. Because it always irritates me when I need to fix that. Anything that creates a new value don't need a reference. That excludes mostly field accesses and indexing. I had a thought that we can also not generate a reference for fields and indexing as long as the type is `Copy`, but sometimes people impl `Copy` even when they don't want to copy the values (e.g. a large type), so I didn't do that.
2 parents b6d0fd0 + 3ff3d39 commit cd377d9

File tree

2 files changed

+140
-10
lines changed

2 files changed

+140
-10
lines changed

src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs

+139-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
2020
// ->
2121
// ```
2222
// fn main() {
23-
// let $0var_name = (1 + 2);
23+
// let $0var_name = 1 + 2;
2424
// var_name * 4;
2525
// }
2626
// ```
@@ -58,9 +58,30 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
5858
}
5959

6060
let parent = to_extract.syntax().parent().and_then(ast::Expr::cast);
61-
let needs_adjust = parent
62-
.as_ref()
63-
.map_or(false, |it| matches!(it, ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_)));
61+
// Any expression that autoderefs may need adjustment.
62+
let mut needs_adjust = parent.as_ref().map_or(false, |it| match it {
63+
ast::Expr::FieldExpr(_)
64+
| ast::Expr::MethodCallExpr(_)
65+
| ast::Expr::CallExpr(_)
66+
| ast::Expr::AwaitExpr(_) => true,
67+
ast::Expr::IndexExpr(index) if index.base().as_ref() == Some(&to_extract) => true,
68+
_ => false,
69+
});
70+
let mut to_extract_no_ref = peel_parens(to_extract.clone());
71+
let needs_ref = needs_adjust
72+
&& match &to_extract_no_ref {
73+
ast::Expr::FieldExpr(_)
74+
| ast::Expr::IndexExpr(_)
75+
| ast::Expr::MacroExpr(_)
76+
| ast::Expr::ParenExpr(_)
77+
| ast::Expr::PathExpr(_) => true,
78+
ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::UnaryOp::Deref) => {
79+
to_extract_no_ref = prefix.expr()?;
80+
needs_adjust = false;
81+
false
82+
}
83+
_ => false,
84+
};
6485

6586
let anchor = Anchor::from(&to_extract)?;
6687
let target = to_extract.syntax().text_range();
@@ -87,22 +108,28 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
87108
Some(ast::Expr::RefExpr(expr)) if expr.mut_token().is_some() => {
88109
make::ident_pat(false, true, make::name(&var_name))
89110
}
111+
_ if needs_adjust
112+
&& !needs_ref
113+
&& ty.as_ref().is_some_and(|ty| ty.is_mutable_reference()) =>
114+
{
115+
make::ident_pat(false, true, make::name(&var_name))
116+
}
90117
_ => make::ident_pat(false, false, make::name(&var_name)),
91118
};
92119

93-
let to_extract = match ty.as_ref().filter(|_| needs_adjust) {
120+
let to_extract_no_ref = match ty.as_ref().filter(|_| needs_ref) {
94121
Some(receiver_type) if receiver_type.is_mutable_reference() => {
95-
make::expr_ref(to_extract, true)
122+
make::expr_ref(to_extract_no_ref, true)
96123
}
97124
Some(receiver_type) if receiver_type.is_reference() => {
98-
make::expr_ref(to_extract, false)
125+
make::expr_ref(to_extract_no_ref, false)
99126
}
100-
_ => to_extract,
127+
_ => to_extract_no_ref,
101128
};
102129

103130
let expr_replace = edit.make_syntax_mut(expr_replace);
104131
let let_stmt =
105-
make::let_stmt(ident_pat.into(), None, Some(to_extract)).clone_for_update();
132+
make::let_stmt(ident_pat.into(), None, Some(to_extract_no_ref)).clone_for_update();
106133
let name_expr = make::expr_path(make::ext::ident_path(&var_name)).clone_for_update();
107134

108135
match anchor {
@@ -202,6 +229,14 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
202229
)
203230
}
204231

232+
fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
233+
while let ast::Expr::ParenExpr(parens) = &expr {
234+
let Some(expr_inside) = parens.expr() else { break };
235+
expr = expr_inside;
236+
}
237+
expr
238+
}
239+
205240
/// Check whether the node is a valid expression which can be extracted to a variable.
206241
/// In general that's true for any expression, but in some cases that would produce invalid code.
207242
fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
@@ -1220,6 +1255,45 @@ fn foo(s: &S) {
12201255
);
12211256
}
12221257

1258+
#[test]
1259+
fn test_extract_var_index_deref() {
1260+
check_assist(
1261+
extract_variable,
1262+
r#"
1263+
//- minicore: index
1264+
struct X;
1265+
1266+
impl std::ops::Index<usize> for X {
1267+
type Output = i32;
1268+
fn index(&self) -> &Self::Output { 0 }
1269+
}
1270+
1271+
struct S {
1272+
sub: X
1273+
}
1274+
1275+
fn foo(s: &S) {
1276+
$0s.sub$0[0];
1277+
}"#,
1278+
r#"
1279+
struct X;
1280+
1281+
impl std::ops::Index<usize> for X {
1282+
type Output = i32;
1283+
fn index(&self) -> &Self::Output { 0 }
1284+
}
1285+
1286+
struct S {
1287+
sub: X
1288+
}
1289+
1290+
fn foo(s: &S) {
1291+
let $0sub = &s.sub;
1292+
sub[0];
1293+
}"#,
1294+
);
1295+
}
1296+
12231297
#[test]
12241298
fn test_extract_var_reference_parameter_deep_nesting() {
12251299
check_assist(
@@ -1461,4 +1535,60 @@ fn foo() {
14611535
}"#,
14621536
);
14631537
}
1538+
1539+
#[test]
1540+
fn generates_no_ref_on_calls() {
1541+
check_assist(
1542+
extract_variable,
1543+
r#"
1544+
struct S;
1545+
impl S {
1546+
fn do_work(&mut self) {}
1547+
}
1548+
fn bar() -> S { S }
1549+
fn foo() {
1550+
$0bar()$0.do_work();
1551+
}"#,
1552+
r#"
1553+
struct S;
1554+
impl S {
1555+
fn do_work(&mut self) {}
1556+
}
1557+
fn bar() -> S { S }
1558+
fn foo() {
1559+
let mut $0bar = bar();
1560+
bar.do_work();
1561+
}"#,
1562+
);
1563+
}
1564+
1565+
#[test]
1566+
fn generates_no_ref_for_deref() {
1567+
check_assist(
1568+
extract_variable,
1569+
r#"
1570+
struct S;
1571+
impl S {
1572+
fn do_work(&mut self) {}
1573+
}
1574+
fn bar() -> S { S }
1575+
fn foo() {
1576+
let v = &mut &mut bar();
1577+
$0(**v)$0.do_work();
1578+
}
1579+
"#,
1580+
r#"
1581+
struct S;
1582+
impl S {
1583+
fn do_work(&mut self) {}
1584+
}
1585+
fn bar() -> S { S }
1586+
fn foo() {
1587+
let v = &mut &mut bar();
1588+
let $0s = *v;
1589+
s.do_work();
1590+
}
1591+
"#,
1592+
);
1593+
}
14641594
}

src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1028,7 +1028,7 @@ fn main() {
10281028
"#####,
10291029
r#####"
10301030
fn main() {
1031-
let $0var_name = (1 + 2);
1031+
let $0var_name = 1 + 2;
10321032
var_name * 4;
10331033
}
10341034
"#####,

0 commit comments

Comments
 (0)