Skip to content

Commit c989287

Browse files
author
Brandon
committed
Fix extract function's mutability of variables outliving the body
1 parent 7278108 commit c989287

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)