Skip to content

Commit 868aa7c

Browse files
authored
Rollup merge of rust-lang#59708 - matthewjasper:double-closure-unused-mut, r=pnkfelix
Mark variables captured by reference as mutable correctly Closes rust-lang#59620 r? @pnkfelix
2 parents e0e0a90 + 968ea1c commit 868aa7c

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
@@ -30,7 +30,7 @@ use std::rc::Rc;
3030
use syntax_pos::{Span, DUMMY_SP};
3131

3232
use crate::dataflow::indexes::{BorrowIndex, InitIndex, MoveOutIndex, MovePathIndex};
33-
use crate::dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MoveError};
33+
use crate::dataflow::move_paths::{HasMoveData, InitLocation, LookupResult, MoveData, MoveError};
3434
use crate::dataflow::Borrows;
3535
use crate::dataflow::DataflowResultsConsumer;
3636
use crate::dataflow::FlowAtLocation;
@@ -1277,25 +1277,7 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
12771277
} = self.infcx.tcx.mir_borrowck(def_id);
12781278
debug!("{:?} used_mut_upvars={:?}", def_id, used_mut_upvars);
12791279
for field in used_mut_upvars {
1280-
// This relies on the current way that by-value
1281-
// captures of a closure are copied/moved directly
1282-
// when generating MIR.
1283-
match operands[field.index()] {
1284-
Operand::Move(Place::Base(PlaceBase::Local(local)))
1285-
| Operand::Copy(Place::Base(PlaceBase::Local(local))) => {
1286-
self.used_mut.insert(local);
1287-
}
1288-
Operand::Move(ref place @ Place::Projection(_))
1289-
| Operand::Copy(ref place @ Place::Projection(_)) => {
1290-
if let Some(field) = place.is_upvar_field_projection(
1291-
self.mir, &self.infcx.tcx) {
1292-
self.used_mut_upvars.push(field);
1293-
}
1294-
}
1295-
Operand::Move(Place::Base(PlaceBase::Static(..)))
1296-
| Operand::Copy(Place::Base(PlaceBase::Static(..)))
1297-
| Operand::Constant(..) => {}
1298-
}
1280+
self.propagate_closure_used_mut_upvar(&operands[field.index()]);
12991281
}
13001282
}
13011283
AggregateKind::Adt(..)
@@ -1310,6 +1292,80 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
13101292
}
13111293
}
13121294

1295+
fn propagate_closure_used_mut_upvar(&mut self, operand: &Operand<'tcx>) {
1296+
let propagate_closure_used_mut_place = |this: &mut Self, place: &Place<'tcx>| {
1297+
match *place {
1298+
Place::Projection { .. } => {
1299+
if let Some(field) = place.is_upvar_field_projection(
1300+
this.mir, &this.infcx.tcx) {
1301+
this.used_mut_upvars.push(field);
1302+
}
1303+
}
1304+
Place::Base(PlaceBase::Local(local)) => {
1305+
this.used_mut.insert(local);
1306+
}
1307+
Place::Base(PlaceBase::Static(_)) => {}
1308+
}
1309+
};
1310+
1311+
// This relies on the current way that by-value
1312+
// captures of a closure are copied/moved directly
1313+
// when generating MIR.
1314+
match *operand {
1315+
Operand::Move(Place::Base(PlaceBase::Local(local)))
1316+
| Operand::Copy(Place::Base(PlaceBase::Local(local)))
1317+
if self.mir.local_decls[local].is_user_variable.is_none() =>
1318+
{
1319+
if self.mir.local_decls[local].ty.is_mutable_pointer() {
1320+
// The variable will be marked as mutable by the borrow.
1321+
return;
1322+
}
1323+
// This is an edge case where we have a `move` closure
1324+
// inside a non-move closure, and the inner closure
1325+
// contains a mutation:
1326+
//
1327+
// let mut i = 0;
1328+
// || { move || { i += 1; }; };
1329+
//
1330+
// In this case our usual strategy of assuming that the
1331+
// variable will be captured by mutable reference is
1332+
// wrong, since `i` can be copied into the inner
1333+
// closure from a shared reference.
1334+
//
1335+
// As such we have to search for the local that this
1336+
// capture comes from and mark it as being used as mut.
1337+
1338+
let temp_mpi = self.move_data.rev_lookup.find_local(local);
1339+
let init = if let [init_index] = *self.move_data.init_path_map[temp_mpi] {
1340+
&self.move_data.inits[init_index]
1341+
} else {
1342+
bug!("temporary should be initialized exactly once")
1343+
};
1344+
1345+
let loc = match init.location {
1346+
InitLocation::Statement(stmt) => stmt,
1347+
_ => bug!("temporary initialized in arguments"),
1348+
};
1349+
1350+
let bbd = &self.mir[loc.block];
1351+
let stmt = &bbd.statements[loc.statement_index];
1352+
debug!("temporary assigned in: stmt={:?}", stmt);
1353+
1354+
if let StatementKind::Assign(_, box Rvalue::Ref(_, _, ref source)) = stmt.kind {
1355+
propagate_closure_used_mut_place(self, source);
1356+
} else {
1357+
bug!("closures should only capture user variables \
1358+
or references to user variables");
1359+
}
1360+
}
1361+
Operand::Move(ref place)
1362+
| Operand::Copy(ref place) => {
1363+
propagate_closure_used_mut_place(self, place);
1364+
}
1365+
Operand::Constant(..) => {}
1366+
}
1367+
}
1368+
13131369
fn consume_operand(
13141370
&mut self,
13151371
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)