Skip to content

Commit a8a2586

Browse files
bors[bot]Brandon
and
Brandon
authored
Merge #8436
8436: Fix extract function's mutability of variables outliving the body r=matklad a=brandondong **Reproduction:** ```rust fn main() { let mut k = 1; let mut j = 2; j += 1; k += j; } ``` 1. Select the first to third lines of the main function. Use the "Extract into function" code assist. 2. The output is the following which does not compile because the outlived variable `k` is declared as immutable: ```rust fn main() { let (k, j) = fun_name(); k += j; } fn fun_name() -> (i32, i32) { let mut k = 1; let mut j = 2; j += 1; (k, j) } ``` 3. We would instead expect the output to be: ```rust fn main() { let (mut k, j) = fun_name(); k += j; } ``` **Fix:** - Instead of declaring outlived variables as immutable unconditionally, check for any mutable usages outside of the extracted function. Co-authored-by: Brandon <[email protected]>
2 parents bd675c8 + c989287 commit a8a2586

File tree

1 file changed

+109
-19
lines changed

1 file changed

+109
-19
lines changed

crates/ide_assists/src/handlers/extract_function.rs

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
7575
let insert_after = scope_for_fn_insertion(&body, anchor)?;
7676
let module = ctx.sema.scope(&insert_after).module()?;
7777

78-
let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body);
78+
let vars_defined_in_body_and_outlive =
79+
vars_defined_in_body_and_outlive(ctx, &body, &node.parent().as_ref().unwrap_or(&node));
7980
let ret_ty = body_return_ty(ctx, &body)?;
8081

8182
// FIXME: we compute variables that outlive here just to check `never!` condition
@@ -257,7 +258,7 @@ struct Function {
257258
control_flow: ControlFlow,
258259
ret_ty: RetType,
259260
body: FunctionBody,
260-
vars_defined_in_body_and_outlive: Vec<Local>,
261+
vars_defined_in_body_and_outlive: Vec<OutlivedLocal>,
261262
}
262263

263264
#[derive(Debug)]
@@ -296,9 +297,9 @@ impl Function {
296297
RetType::Expr(ty) => FunType::Single(ty.clone()),
297298
RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() {
298299
[] => FunType::Unit,
299-
[var] => FunType::Single(var.ty(ctx.db())),
300+
[var] => FunType::Single(var.local.ty(ctx.db())),
300301
vars => {
301-
let types = vars.iter().map(|v| v.ty(ctx.db())).collect();
302+
let types = vars.iter().map(|v| v.local.ty(ctx.db())).collect();
302303
FunType::Tuple(types)
303304
}
304305
},
@@ -562,6 +563,12 @@ impl HasTokenAtOffset for FunctionBody {
562563
}
563564
}
564565

566+
#[derive(Debug)]
567+
struct OutlivedLocal {
568+
local: Local,
569+
mut_usage_outside_body: bool,
570+
}
571+
565572
/// Try to guess what user wants to extract
566573
///
567574
/// We have basically have two cases:
@@ -707,10 +714,10 @@ fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &Functi
707714
.any(|reference| reference_is_exclusive(reference, body, ctx))
708715
}
709716

710-
/// checks if this reference requires `&mut` access inside body
717+
/// checks if this reference requires `&mut` access inside node
711718
fn reference_is_exclusive(
712719
reference: &FileReference,
713-
body: &FunctionBody,
720+
node: &dyn HasTokenAtOffset,
714721
ctx: &AssistContext,
715722
) -> bool {
716723
// we directly modify variable with set: `n = 0`, `n += 1`
@@ -719,7 +726,7 @@ fn reference_is_exclusive(
719726
}
720727

721728
// we take `&mut` reference to variable: `&mut v`
722-
let path = match path_element_of_reference(body, reference) {
729+
let path = match path_element_of_reference(node, reference) {
723730
Some(path) => path,
724731
None => return false,
725732
};
@@ -820,10 +827,16 @@ fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local>
820827
}
821828

822829
/// list local variables defined inside `body` that should be returned from extracted function
823-
fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> {
824-
let mut vars_defined_in_body = vars_defined_in_body(&body, ctx);
825-
vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var));
830+
fn vars_defined_in_body_and_outlive(
831+
ctx: &AssistContext,
832+
body: &FunctionBody,
833+
parent: &SyntaxNode,
834+
) -> Vec<OutlivedLocal> {
835+
let vars_defined_in_body = vars_defined_in_body(&body, ctx);
826836
vars_defined_in_body
837+
.into_iter()
838+
.filter_map(|var| var_outlives_body(ctx, body, var, parent))
839+
.collect()
827840
}
828841

829842
/// checks if the relevant local was defined before(outside of) body
@@ -843,11 +856,23 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
843856
}
844857
}
845858

846-
/// checks if local variable is used after(outside of) body
847-
fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool {
848-
let usages = LocalUsages::find(ctx, *var);
859+
/// returns usage details if local variable is used after(outside of) body
860+
fn var_outlives_body(
861+
ctx: &AssistContext,
862+
body: &FunctionBody,
863+
var: Local,
864+
parent: &SyntaxNode,
865+
) -> Option<OutlivedLocal> {
866+
let usages = LocalUsages::find(ctx, var);
849867
let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range));
850-
has_usages
868+
if !has_usages {
869+
return None;
870+
}
871+
let has_mut_usages = usages
872+
.iter()
873+
.filter(|reference| body.preceedes_range(reference.range))
874+
.any(|reference| reference_is_exclusive(reference, parent, ctx));
875+
Some(OutlivedLocal { local: var, mut_usage_outside_body: has_mut_usages })
851876
}
852877

853878
fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> {
@@ -927,16 +952,25 @@ fn format_replacement(ctx: &AssistContext, fun: &Function, indent: IndentLevel)
927952
let mut buf = String::new();
928953
match fun.vars_defined_in_body_and_outlive.as_slice() {
929954
[] => {}
930-
[var] => format_to!(buf, "let {} = ", var.name(ctx.db()).unwrap()),
955+
[var] => {
956+
format_to!(buf, "let {}{} = ", mut_modifier(var), var.local.name(ctx.db()).unwrap())
957+
}
931958
[v0, vs @ ..] => {
932959
buf.push_str("let (");
933-
format_to!(buf, "{}", v0.name(ctx.db()).unwrap());
960+
format_to!(buf, "{}{}", mut_modifier(v0), v0.local.name(ctx.db()).unwrap());
934961
for var in vs {
935-
format_to!(buf, ", {}", var.name(ctx.db()).unwrap());
962+
format_to!(buf, ", {}{}", mut_modifier(var), var.local.name(ctx.db()).unwrap());
936963
}
937964
buf.push_str(") = ");
938965
}
939966
}
967+
fn mut_modifier(var: &OutlivedLocal) -> &'static str {
968+
if var.mut_usage_outside_body {
969+
"mut "
970+
} else {
971+
""
972+
}
973+
}
940974
format_to!(buf, "{}", expr);
941975
if fun.ret_ty.is_unit()
942976
&& (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like())
@@ -1199,10 +1233,10 @@ fn make_body(
11991233
match fun.vars_defined_in_body_and_outlive.as_slice() {
12001234
[] => {}
12011235
[var] => {
1202-
tail_expr = Some(path_expr_from_local(ctx, *var));
1236+
tail_expr = Some(path_expr_from_local(ctx, var.local));
12031237
}
12041238
vars => {
1205-
let exprs = vars.iter().map(|var| path_expr_from_local(ctx, *var));
1239+
let exprs = vars.iter().map(|var| path_expr_from_local(ctx, var.local));
12061240
let expr = make::expr_tuple(exprs);
12071241
tail_expr = Some(expr);
12081242
}
@@ -2110,6 +2144,30 @@ fn $0fun_name(n: i32) -> i32 {
21102144
);
21112145
}
21122146

2147+
#[test]
2148+
fn variable_defined_inside_and_used_after_mutably_no_ret() {
2149+
check_assist(
2150+
extract_function,
2151+
r"
2152+
fn foo() {
2153+
let n = 1;
2154+
$0let mut k = n * n;$0
2155+
k += 1;
2156+
}",
2157+
r"
2158+
fn foo() {
2159+
let n = 1;
2160+
let mut k = fun_name(n);
2161+
k += 1;
2162+
}
2163+
2164+
fn $0fun_name(n: i32) -> i32 {
2165+
let mut k = n * n;
2166+
k
2167+
}",
2168+
);
2169+
}
2170+
21132171
#[test]
21142172
fn two_variables_defined_inside_and_used_after_no_ret() {
21152173
check_assist(
@@ -2136,6 +2194,38 @@ fn $0fun_name(n: i32) -> (i32, i32) {
21362194
);
21372195
}
21382196

2197+
#[test]
2198+
fn multi_variables_defined_inside_and_used_after_mutably_no_ret() {
2199+
check_assist(
2200+
extract_function,
2201+
r"
2202+
fn foo() {
2203+
let n = 1;
2204+
$0let mut k = n * n;
2205+
let mut m = k + 2;
2206+
let mut o = m + 3;
2207+
o += 1;$0
2208+
k += o;
2209+
m = 1;
2210+
}",
2211+
r"
2212+
fn foo() {
2213+
let n = 1;
2214+
let (mut k, mut m, o) = fun_name(n);
2215+
k += o;
2216+
m = 1;
2217+
}
2218+
2219+
fn $0fun_name(n: i32) -> (i32, i32, i32) {
2220+
let mut k = n * n;
2221+
let mut m = k + 2;
2222+
let mut o = m + 3;
2223+
o += 1;
2224+
(k, m, o)
2225+
}",
2226+
);
2227+
}
2228+
21392229
#[test]
21402230
fn nontrivial_patterns_define_variables() {
21412231
check_assist(

0 commit comments

Comments
 (0)