MIR changes to improve NLL cannot mutate errors

Alway use unique instead of mutable borrows immutable upvars.
Mark variables that are references for a match guard
This commit is contained in:
Matthew Jasper 2018-07-15 15:00:58 +01:00
parent 509cbf3e8e
commit c3dbdfee06
5 changed files with 190 additions and 50 deletions

View File

@ -531,6 +531,8 @@ pub enum BindingForm<'tcx> {
Var(VarBindingForm<'tcx>),
/// Binding for a `self`/`&self`/`&mut self` binding where the type is implicit.
ImplicitSelf,
/// Reference used in a guard expression to ensure immutability.
RefForGuard,
}
CloneTypeFoldableAndLiftImpls! { BindingForm<'tcx>, }
@ -555,6 +557,7 @@ mod binding_form_impl {
match self {
Var(binding) => binding.hash_stable(hcx, hasher),
ImplicitSelf => (),
RefForGuard => (),
}
}
}

View File

@ -742,6 +742,24 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
autoderef,
&including_downcast,
)?;
} else if let Place::Local(local) = proj.base {
if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard))
= self.mir.local_decls[local].is_user_variable {
self.append_place_to_string(
&proj.base,
buf,
autoderef,
&including_downcast,
)?;
} else {
buf.push_str(&"*");
self.append_place_to_string(
&proj.base,
buf,
autoderef,
&including_downcast,
)?;
}
} else {
buf.push_str(&"*");
self.append_place_to_string(

View File

@ -206,7 +206,27 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
this.consume_by_copy_or_move(place)
}
_ => {
unpack!(block = this.as_operand(block, scope, upvar))
// Turn mutable borrow captures into unique
// borrow captures when capturing an immutable
// variable. This is sound because the mutation
// that caused the capture will cause an error.
match upvar.kind {
ExprKind::Borrow {
borrow_kind: BorrowKind::Mut {
allow_two_phase_borrow: false
},
region,
arg,
} => unpack!(block = this.limit_capture_mutability(
upvar.span,
upvar.ty,
scope,
block,
arg,
region,
)),
_ => unpack!(block = this.as_operand(block, scope, upvar)),
}
}
}
})
@ -393,6 +413,101 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
}
}
fn limit_capture_mutability(
&mut self,
upvar_span: Span,
upvar_ty: Ty<'tcx>,
temp_lifetime: Option<region::Scope>,
mut block: BasicBlock,
arg: ExprRef<'tcx>,
region: &'tcx ty::RegionKind,
) -> BlockAnd<Operand<'tcx>> {
let this = self;
let source_info = this.source_info(upvar_span);
let temp = this.local_decls.push(LocalDecl::new_temp(upvar_ty, upvar_span));
this.cfg.push(block, Statement {
source_info,
kind: StatementKind::StorageLive(temp)
});
let arg_place = unpack!(block = this.as_place(block, arg));
let mutability = match arg_place {
Place::Local(local) => this.local_decls[local].mutability,
Place::Projection(box Projection {
base: Place::Local(local),
elem: ProjectionElem::Deref,
}) => {
debug_assert!(
if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard))
= this.local_decls[local].is_user_variable {
true
} else {
false
},
"Unexpected capture place",
);
this.local_decls[local].mutability
}
Place::Projection(box Projection {
ref base,
elem: ProjectionElem::Field(upvar_index, _),
})
| Place::Projection(box Projection {
base: Place::Projection(box Projection {
ref base,
elem: ProjectionElem::Field(upvar_index, _),
}),
elem: ProjectionElem::Deref,
}) => {
// Not projected from the implicit `self` in a closure.
debug_assert!(
match *base {
Place::Local(local) => local == Local::new(1),
Place::Projection(box Projection {
ref base,
elem: ProjectionElem::Deref,
}) => *base == Place::Local(Local::new(1)),
_ => false,
},
"Unexpected capture place"
);
// Not in a closure
debug_assert!(
this.upvar_decls.len() > upvar_index.index(),
"Unexpected capture place"
);
this.upvar_decls[upvar_index.index()].mutability
}
_ => bug!("Unexpected capture place"),
};
let borrow_kind = match mutability {
Mutability::Not => BorrowKind::Unique,
Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false },
};
this.cfg.push_assign(
block,
source_info,
&Place::Local(temp),
Rvalue::Ref(region, borrow_kind, arg_place),
);
// In constants, temp_lifetime is None. We should not need to drop
// anything because no values with a destructor can be created in
// a constant at this time, even if the type may need dropping.
if let Some(temp_lifetime) = temp_lifetime {
this.schedule_drop_storage_and_value(
upvar_span, temp_lifetime, &Place::Local(temp), upvar_ty,
);
}
block.and(Operand::Move(Place::Local(temp)))
}
// Helper to get a `-1` value of the appropriate type
fn neg_1_literal(&mut self, span: Span, ty: Ty<'tcx>) -> Operand<'tcx> {
let param_ty = ty::ParamEnv::empty().and(self.hir.tcx().lift_to_global(&ty).unwrap());

View File

@ -1198,7 +1198,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
visibility_scope,
// FIXME: should these secretly injected ref_for_guard's be marked as `internal`?
internal: false,
is_user_variable: None,
is_user_variable: Some(ClearCrossCrate::Set(BindingForm::RefForGuard)),
});
LocalsForNode::Three { val_for_guard, ref_for_guard, for_arm_body }
} else {

View File

@ -286,6 +286,7 @@ struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
/// (A match binding can have two locals; the 2nd is for the arm's guard.)
var_indices: NodeMap<LocalsForNode>,
local_decls: IndexVec<Local, LocalDecl<'tcx>>,
upvar_decls: Vec<UpvarDecl>,
unit_temp: Option<Place<'tcx>>,
/// cached block with the RESUME terminator; this is created
@ -472,11 +473,52 @@ fn construct_fn<'a, 'gcx, 'tcx, A>(hir: Cx<'a, 'gcx, 'tcx>,
let tcx = hir.tcx();
let span = tcx.hir.span(fn_id);
// Gather the upvars of a closure, if any.
let upvar_decls: Vec<_> = tcx.with_freevars(fn_id, |freevars| {
freevars.iter().map(|fv| {
let var_id = fv.var_id();
let var_hir_id = tcx.hir.node_to_hir_id(var_id);
let closure_expr_id = tcx.hir.local_def_id(fn_id);
let capture = hir.tables().upvar_capture(ty::UpvarId {
var_id: var_hir_id,
closure_expr_id: LocalDefId::from_def_id(closure_expr_id),
});
let by_ref = match capture {
ty::UpvarCapture::ByValue => false,
ty::UpvarCapture::ByRef(..) => true
};
let mut decl = UpvarDecl {
debug_name: keywords::Invalid.name(),
var_hir_id: ClearCrossCrate::Set(var_hir_id),
by_ref,
mutability: Mutability::Not,
};
if let Some(hir::map::NodeBinding(pat)) = tcx.hir.find(var_id) {
if let hir::PatKind::Binding(_, _, ident, _) = pat.node {
decl.debug_name = ident.name;
if let Some(&bm) = hir.tables.pat_binding_modes().get(pat.hir_id) {
if bm == ty::BindByValue(hir::MutMutable) {
decl.mutability = Mutability::Mut;
} else {
decl.mutability = Mutability::Not;
}
} else {
tcx.sess.delay_span_bug(pat.span, "missing binding mode");
}
}
}
decl
}).collect()
});
let mut builder = Builder::new(hir.clone(),
span,
arguments.len(),
safety,
return_ty);
return_ty,
upvar_decls);
let fn_def_id = tcx.hir.local_def_id(fn_id);
let call_site_scope = region::Scope::CallSite(body.value.hir_id.local_id);
@ -519,46 +561,7 @@ fn construct_fn<'a, 'gcx, 'tcx, A>(hir: Cx<'a, 'gcx, 'tcx>,
info!("fn_id {:?} has attrs {:?}", closure_expr_id,
tcx.get_attrs(closure_expr_id));
// Gather the upvars of a closure, if any.
let upvar_decls: Vec<_> = tcx.with_freevars(fn_id, |freevars| {
freevars.iter().map(|fv| {
let var_id = fv.var_id();
let var_hir_id = tcx.hir.node_to_hir_id(var_id);
let closure_expr_id = tcx.hir.local_def_id(fn_id);
let capture = hir.tables().upvar_capture(ty::UpvarId {
var_id: var_hir_id,
closure_expr_id: LocalDefId::from_def_id(closure_expr_id),
});
let by_ref = match capture {
ty::UpvarCapture::ByValue => false,
ty::UpvarCapture::ByRef(..) => true
};
let mut decl = UpvarDecl {
debug_name: keywords::Invalid.name(),
var_hir_id: ClearCrossCrate::Set(var_hir_id),
by_ref,
mutability: Mutability::Not,
};
if let Some(hir::map::NodeBinding(pat)) = tcx.hir.find(var_id) {
if let hir::PatKind::Binding(_, _, ident, _) = pat.node {
decl.debug_name = ident.name;
if let Some(&bm) = hir.tables.pat_binding_modes().get(pat.hir_id) {
if bm == ty::BindByValue(hir::MutMutable) {
decl.mutability = Mutability::Mut;
} else {
decl.mutability = Mutability::Not;
}
} else {
tcx.sess.delay_span_bug(pat.span, "missing binding mode");
}
}
}
decl
}).collect()
});
let mut mir = builder.finish(upvar_decls, yield_ty);
let mut mir = builder.finish(yield_ty);
mir.spread_arg = spread_arg;
mir
}
@ -571,7 +574,7 @@ fn construct_const<'a, 'gcx, 'tcx>(hir: Cx<'a, 'gcx, 'tcx>,
let ty = hir.tables().expr_ty_adjusted(ast_expr);
let owner_id = tcx.hir.body_owner(body_id);
let span = tcx.hir.span(owner_id);
let mut builder = Builder::new(hir.clone(), span, 0, Safety::Safe, ty);
let mut builder = Builder::new(hir.clone(), span, 0, Safety::Safe, ty, vec![]);
let mut block = START_BLOCK;
let expr = builder.hir.mirror(ast_expr);
@ -590,7 +593,7 @@ fn construct_const<'a, 'gcx, 'tcx>(hir: Cx<'a, 'gcx, 'tcx>,
TerminatorKind::Unreachable);
}
builder.finish(vec![], None)
builder.finish(None)
}
fn construct_error<'a, 'gcx, 'tcx>(hir: Cx<'a, 'gcx, 'tcx>,
@ -599,10 +602,10 @@ fn construct_error<'a, 'gcx, 'tcx>(hir: Cx<'a, 'gcx, 'tcx>,
let owner_id = hir.tcx().hir.body_owner(body_id);
let span = hir.tcx().hir.span(owner_id);
let ty = hir.tcx().types.err;
let mut builder = Builder::new(hir, span, 0, Safety::Safe, ty);
let mut builder = Builder::new(hir, span, 0, Safety::Safe, ty, vec![]);
let source_info = builder.source_info(span);
builder.cfg.terminate(START_BLOCK, source_info, TerminatorKind::Unreachable);
builder.finish(vec![], None)
builder.finish(None)
}
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
@ -610,7 +613,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
span: Span,
arg_count: usize,
safety: Safety,
return_ty: Ty<'tcx>)
return_ty: Ty<'tcx>,
upvar_decls: Vec<UpvarDecl>)
-> Builder<'a, 'gcx, 'tcx> {
let lint_level = LintLevel::Explicit(hir.root_lint_level);
let mut builder = Builder {
@ -628,6 +632,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
breakable_scopes: vec![],
local_decls: IndexVec::from_elem_n(LocalDecl::new_return_place(return_ty,
span), 1),
upvar_decls,
var_indices: NodeMap(),
unit_temp: None,
cached_resume_block: None,
@ -645,7 +650,6 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
}
fn finish(self,
upvar_decls: Vec<UpvarDecl>,
yield_ty: Option<Ty<'tcx>>)
-> Mir<'tcx> {
for (index, block) in self.cfg.basic_blocks.iter().enumerate() {
@ -661,7 +665,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
yield_ty,
self.local_decls,
self.arg_count,
upvar_decls,
self.upvar_decls,
self.fn_span
)
}