Skip to content

Commit dbb1c1b

Browse files
bors[bot]Vannevelj
andauthored
Merge #11184
11184: Correctly pass through mutable parameter references when extracting a function r=Veykril a=Vannevelj Fixes #10277 I have based this investigation based on my understanding of [the Borrowing chapter](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html) but I wasn't able to debug the test runs or see it in action in an IDE. I'll try to figure out how to do that for future PRs but for now, the tests seem to confirm my understanding. I'll lay out my hypothesis below. Here we define the parameters for the to-be-generated function: https://github.com/rust-analyzer/rust-analyzer/blob/7409880a07803c34590ad162d7854061145c6eae/crates/ide_assists/src/handlers/extract_function.rs#L882 Three values in particular are important here: `requires_mut`, `is_copy` and `move_local`. These will in turn be used here to determine the kind of parameter: https://github.com/rust-analyzer/rust-analyzer/blob/7409880a07803c34590ad162d7854061145c6eae/crates/ide_assists/src/handlers/extract_function.rs#L374-L381 and then here to determine what transformation is needed for the calling argument: https://github.com/rust-analyzer/rust-analyzer/blob/7409880a07803c34590ad162d7854061145c6eae/crates/ide_assists/src/handlers/extract_function.rs#L383-L390 which then gets transformed here: https://github.com/rust-analyzer/rust-analyzer/blob/7409880a07803c34590ad162d7854061145c6eae/crates/syntax/src/ast/make.rs#L381-L383 What I believe is happening is that * `requires_mut` is `false` (it already is marked as mutable), * `is_copy` is `false` (`Foo` does not implement `Copy`), and * `move_local` is `false` (it has further usages) According to the pattern matching in `fn kind()`, that would lead to `ParamKind::SharedRef` which in turn applies a transformation that prepends `&`. However if I look at the chapter on borrowing then we only need to mark an argument as a reference if we actually own it. In this case the value is passed through as a reference parameter into the current function which means we never had ownership in the first place. By including the additional check for a reference parameter, `move_local` now becomes `true` and the resulting parameter is now `ParamKind::Value` which will avoid applying any transformations. This was further obscured by the fact that you need further usages of the variable or `move_local` would be considered `true` after all. I didn't follow it in depth but it appears this idea applies for both the generated argument and the generated parameter. There are existing tests that account for `&mut` values but they refer to local variables for which we do have ownership and as such they didn't expose this issue. Co-authored-by: Jeroen Vannevel <[email protected]>
2 parents 22edf2e + ec61abb commit dbb1c1b

File tree

1 file changed

+63
-1
lines changed

1 file changed

+63
-1
lines changed

crates/ide_assists/src/handlers/extract_function.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,7 @@ impl FunctionBody {
878878
// We can move the value into the function call if it's not used after the call,
879879
// if the var is not used but defined outside a loop we are extracting from we can't move it either
880880
// as the function will reuse it in the next iteration.
881-
let move_local = !has_usages && defined_outside_parent_loop;
881+
let move_local = (!has_usages && defined_outside_parent_loop) || ty.is_reference();
882882
Param { var, ty, move_local, requires_mut, is_copy }
883883
})
884884
.collect()
@@ -4332,6 +4332,68 @@ fn foo() {
43324332
fn $0fun_name(a: _) -> _ {
43334333
a
43344334
}
4335+
"#,
4336+
);
4337+
}
4338+
4339+
#[test]
4340+
fn reference_mutable_param_with_further_usages() {
4341+
check_assist(
4342+
extract_function,
4343+
r#"
4344+
pub struct Foo {
4345+
field: u32,
4346+
}
4347+
4348+
pub fn testfn(arg: &mut Foo) {
4349+
$0arg.field = 8;$0
4350+
// Simulating access after the extracted portion
4351+
arg.field = 16;
4352+
}
4353+
"#,
4354+
r#"
4355+
pub struct Foo {
4356+
field: u32,
4357+
}
4358+
4359+
pub fn testfn(arg: &mut Foo) {
4360+
fun_name(arg);
4361+
// Simulating access after the extracted portion
4362+
arg.field = 16;
4363+
}
4364+
4365+
fn $0fun_name(arg: &mut Foo) {
4366+
arg.field = 8;
4367+
}
4368+
"#,
4369+
);
4370+
}
4371+
4372+
#[test]
4373+
fn reference_mutable_param_without_further_usages() {
4374+
check_assist(
4375+
extract_function,
4376+
r#"
4377+
pub struct Foo {
4378+
field: u32,
4379+
}
4380+
4381+
pub fn testfn(arg: &mut Foo) {
4382+
$0arg.field = 8;$0
4383+
}
4384+
"#,
4385+
r#"
4386+
pub struct Foo {
4387+
field: u32,
4388+
}
4389+
4390+
pub fn testfn(arg: &mut Foo) {
4391+
fun_name(arg);
4392+
}
4393+
4394+
fn $0fun_name(arg: &mut Foo) {
4395+
arg.field = 8;
4396+
}
43354397
"#,
43364398
);
43374399
}

0 commit comments

Comments
 (0)