Skip to content

Commit 968ea1c

Browse files
committed
Mark variables captured by reference as mutable correctly
1 parent f8673e0 commit 968ea1c

File tree

2 files changed

+87
-28
lines changed

2 files changed

+87
-28
lines changed

src/librustc_mir/borrow_check/mod.rs

+76-20
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use std::collections::BTreeMap;
2828
use syntax_pos::Span;
2929

3030
use crate::dataflow::indexes::{BorrowIndex, InitIndex, MoveOutIndex, MovePathIndex};
31-
use crate::dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MoveError};
31+
use crate::dataflow::move_paths::{HasMoveData, InitLocation, LookupResult, MoveData, MoveError};
3232
use crate::dataflow::Borrows;
3333
use crate::dataflow::DataflowResultsConsumer;
3434
use crate::dataflow::FlowAtLocation;
@@ -1206,25 +1206,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
12061206
} = self.infcx.tcx.mir_borrowck(def_id);
12071207
debug!("{:?} used_mut_upvars={:?}", def_id, used_mut_upvars);
12081208
for field in used_mut_upvars {
1209-
// This relies on the current way that by-value
1210-
// captures of a closure are copied/moved directly
1211-
// when generating MIR.
1212-
match operands[field.index()] {
1213-
Operand::Move(Place::Base(PlaceBase::Local(local)))
1214-
| Operand::Copy(Place::Base(PlaceBase::Local(local))) => {
1215-
self.used_mut.insert(local);
1216-
}
1217-
Operand::Move(ref place @ Place::Projection(_))
1218-
| Operand::Copy(ref place @ Place::Projection(_)) => {
1219-
if let Some(field) = place.is_upvar_field_projection(
1220-
self.mir, &self.infcx.tcx) {
1221-
self.used_mut_upvars.push(field);
1222-
}
1223-
}
1224-
Operand::Move(Place::Base(PlaceBase::Static(..)))
1225-
| Operand::Copy(Place::Base(PlaceBase::Static(..)))
1226-
| Operand::Constant(..) => {}
1227-
}
1209+
self.propagate_closure_used_mut_upvar(&operands[field.index()]);
12281210
}
12291211
}
12301212
AggregateKind::Adt(..)
@@ -1239,6 +1221,80 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
12391221
}
12401222
}
12411223

1224+
fn propagate_closure_used_mut_upvar(&mut self, operand: &Operand<'tcx>) {
1225+
let propagate_closure_used_mut_place = |this: &mut Self, place: &Place<'tcx>| {
1226+
match *place {
1227+
Place::Projection { .. } => {
1228+
if let Some(field) = place.is_upvar_field_projection(
1229+
this.mir, &this.infcx.tcx) {
1230+
this.used_mut_upvars.push(field);
1231+
}
1232+
}
1233+
Place::Base(PlaceBase::Local(local)) => {
1234+
this.used_mut.insert(local);
1235+
}
1236+
Place::Base(PlaceBase::Static(_)) => {}
1237+
}
1238+
};
1239+
1240+
// This relies on the current way that by-value
1241+
// captures of a closure are copied/moved directly
1242+
// when generating MIR.
1243+
match *operand {
1244+
Operand::Move(Place::Base(PlaceBase::Local(local)))
1245+
| Operand::Copy(Place::Base(PlaceBase::Local(local)))
1246+
if self.mir.local_decls[local].is_user_variable.is_none() =>
1247+
{
1248+
if self.mir.local_decls[local].ty.is_mutable_pointer() {
1249+
// The variable will be marked as mutable by the borrow.
1250+
return;
1251+
}
1252+
// This is an edge case where we have a `move` closure
1253+
// inside a non-move closure, and the inner closure
1254+
// contains a mutation:
1255+
//
1256+
// let mut i = 0;
1257+
// || { move || { i += 1; }; };
1258+
//
1259+
// In this case our usual strategy of assuming that the
1260+
// variable will be captured by mutable reference is
1261+
// wrong, since `i` can be copied into the inner
1262+
// closure from a shared reference.
1263+
//
1264+
// As such we have to search for the local that this
1265+
// capture comes from and mark it as being used as mut.
1266+
1267+
let temp_mpi = self.move_data.rev_lookup.find_local(local);
1268+
let init = if let [init_index] = *self.move_data.init_path_map[temp_mpi] {
1269+
&self.move_data.inits[init_index]
1270+
} else {
1271+
bug!("temporary should be initialized exactly once")
1272+
};
1273+
1274+
let loc = match init.location {
1275+
InitLocation::Statement(stmt) => stmt,
1276+
_ => bug!("temporary initialized in arguments"),
1277+
};
1278+
1279+
let bbd = &self.mir[loc.block];
1280+
let stmt = &bbd.statements[loc.statement_index];
1281+
debug!("temporary assigned in: stmt={:?}", stmt);
1282+
1283+
if let StatementKind::Assign(_, box Rvalue::Ref(_, _, ref source)) = stmt.kind {
1284+
propagate_closure_used_mut_place(self, source);
1285+
} else {
1286+
bug!("closures should only capture user variables \
1287+
or references to user variables");
1288+
}
1289+
}
1290+
Operand::Move(ref place)
1291+
| Operand::Copy(ref place) => {
1292+
propagate_closure_used_mut_place(self, place);
1293+
}
1294+
Operand::Constant(..) => {}
1295+
}
1296+
}
1297+
12421298
fn consume_operand(
12431299
&mut self,
12441300
context: Context,

src/test/ui/nll/extra-unused-mut.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// extra unused mut lint tests for #51918
22

3-
// run-pass
3+
// compile-pass
44

55
#![feature(generators, nll)]
66
#![deny(unused_mut)]
@@ -53,11 +53,14 @@ fn if_guard(x: Result<i32, i32>) {
5353
}
5454
}
5555

56-
fn main() {
57-
ref_argument(0);
58-
mutable_upvar();
59-
generator_mutable_upvar();
60-
ref_closure_argument();
61-
parse_dot_or_call_expr_with(Vec::new());
62-
if_guard(Ok(0));
56+
// #59620
57+
fn nested_closures() {
58+
let mut i = 0;
59+
[].iter().for_each(|_: &i32| {
60+
[].iter().for_each(move |_: &i32| {
61+
i += 1;
62+
});
63+
});
6364
}
65+
66+
fn main() {}

0 commit comments

Comments
 (0)