Auto merge of #66246 - matthewjasper:simplify-mem-cat, r=pnkfelix

Simplify memory categorization

With AST borrowck gone, mem_categorization can be simplified, a lot.

* `cmt_` is now called `Place`. Most local variable names have been updated to reflect this, but the `cat_*` methods retain their names.
* `MemCategorizationContext` no longer needs a `ScopeTree` and always needs an `InferCtxt`.
* `Place` now uses a similar representation to `mir::Place` with a `Vec` of projections.
* `Upvar` places don't include the implicit environment and capture derefs. These are now handled by `regionck` when needed.
* Various types, methods and variants only used by AST borrowck have been removed.
* `ExprUseVisitor` now lives in `rustc_typeck::expr_use_visitor`.
* `MemCategorizationContext` and `Place` live in `rustc_typeck::mem_categorization`.
* `Place` is re-exported in `rustc_typeck::expr_use_visitor` so that Clippy can access it.

The loss of an error in `issue-4335.rs` is due to a change in capture inference in ill-formed programs. If any projection from a variable is moved from then we capture that variable by move, whether or not the place being moved from allows this.

Closes #66270
This commit is contained in:
bors 2019-11-28 10:36:56 +00:00
commit 4752c05af4
11 changed files with 992 additions and 1956 deletions

View file

@ -96,7 +96,6 @@ pub mod infer;
pub mod lint;
pub mod middle {
pub mod expr_use_visitor;
pub mod cstore;
pub mod dependency_format;
pub mod diagnostic_items;
@ -104,7 +103,6 @@ pub mod middle {
pub mod free_region;
pub mod lib_features;
pub mod lang_items;
pub mod mem_categorization;
pub mod privacy;
pub mod reachable;
pub mod region;

File diff suppressed because it is too large Load diff

View file

@ -74,8 +74,7 @@
use crate::check::dropck;
use crate::check::FnCtxt;
use crate::middle::mem_categorization as mc;
use crate::middle::mem_categorization::Categorization;
use crate::mem_categorization as mc;
use crate::middle::region;
use rustc::hir::def_id::DefId;
use rustc::infer::outlives::env::OutlivesEnvironment;
@ -88,7 +87,6 @@ use rustc::hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc::hir::{self, PatKind};
use std::mem;
use std::ops::Deref;
use std::rc::Rc;
use syntax_pos::Span;
// a variation on try that just returns unit
@ -814,18 +812,17 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
where
F: for<'b> FnOnce(mc::MemCategorizationContext<'b, 'tcx>) -> R,
{
f(mc::MemCategorizationContext::with_infer(
f(mc::MemCategorizationContext::new(
&self.infcx,
self.outlives_environment.param_env,
self.body_owner,
&self.region_scope_tree,
&self.tables.borrow(),
))
}
/// Invoked on any adjustments that occur. Checks that if this is a region pointer being
/// dereferenced, the lifetime of the pointer includes the deref expr.
fn constrain_adjustments(&mut self, expr: &hir::Expr) -> mc::McResult<mc::cmt_<'tcx>> {
fn constrain_adjustments(&mut self, expr: &hir::Expr) -> mc::McResult<mc::Place<'tcx>> {
debug!("constrain_adjustments(expr={:?})", expr);
let mut cmt = self.with_mc(|mc| mc.cat_expr_unadjusted(expr))?;
@ -899,10 +896,6 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
}
cmt = self.with_mc(|mc| mc.cat_expr_adjusted(expr, cmt, &adjustment))?;
if let Categorization::Deref(_, mc::BorrowedPtr(_, r_ptr)) = cmt.cat {
self.mk_subregion_due_to_dereference(expr.span, expr_region, r_ptr);
}
}
Ok(cmt)
@ -921,16 +914,22 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
)
}
fn check_safety_of_rvalue_destructor_if_necessary(&mut self, cmt: &mc::cmt_<'tcx>, span: Span) {
if let Categorization::Rvalue = cmt.cat {
let typ = self.resolve_type(cmt.ty);
let body_id = self.body_id;
let _ = dropck::check_drop_obligations(
self,
typ,
span,
body_id,
);
fn check_safety_of_rvalue_destructor_if_necessary(
&mut self,
place: &mc::Place<'tcx>,
span: Span,
) {
if let mc::PlaceBase::Rvalue = place.base {
if place.projections.is_empty() {
let typ = self.resolve_type(place.ty);
let body_id = self.body_id;
let _ = dropck::check_drop_obligations(
self,
typ,
span,
body_id,
);
}
}
}
@ -1035,7 +1034,7 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
}
Some(ref expr) => &**expr,
};
let discr_cmt = Rc::new(ignore_err!(self.with_mc(|mc| mc.cat_expr(init_expr))));
let discr_cmt = ignore_err!(self.with_mc(|mc| mc.cat_expr(init_expr)));
self.link_pattern(discr_cmt, &local.pat);
}
@ -1044,7 +1043,7 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
/// linked to the lifetime of its guarantor (if any).
fn link_match(&self, discr: &hir::Expr, arms: &[hir::Arm]) {
debug!("regionck::for_match()");
let discr_cmt = Rc::new(ignore_err!(self.with_mc(|mc| mc.cat_expr(discr))));
let discr_cmt = ignore_err!(self.with_mc(|mc| mc.cat_expr(discr)));
debug!("discr_cmt={:?}", discr_cmt);
for arm in arms {
self.link_pattern(discr_cmt.clone(), &arm.pat);
@ -1058,7 +1057,7 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
for param in params {
let param_ty = self.node_ty(param.hir_id);
let param_cmt = self.with_mc(|mc| {
Rc::new(mc.cat_rvalue(param.hir_id, param.pat.span, param_ty))
mc.cat_rvalue(param.hir_id, param.pat.span, param_ty)
});
debug!("param_ty={:?} param_cmt={:?} param={:?}", param_ty, param_cmt, param);
self.link_pattern(param_cmt, &param.pat);
@ -1067,7 +1066,7 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
/// Link lifetimes of any ref bindings in `root_pat` to the pointers found
/// in the discriminant, if needed.
fn link_pattern(&self, discr_cmt: mc::cmt<'tcx>, root_pat: &hir::Pat) {
fn link_pattern(&self, discr_cmt: mc::Place<'tcx>, root_pat: &hir::Pat) {
debug!(
"link_pattern(discr_cmt={:?}, root_pat={:?})",
discr_cmt, root_pat
@ -1100,7 +1099,7 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
fn link_autoref(
&self,
expr: &hir::Expr,
expr_cmt: &mc::cmt_<'tcx>,
expr_cmt: &mc::Place<'tcx>,
autoref: &adjustment::AutoBorrow<'tcx>,
) {
debug!(
@ -1130,7 +1129,7 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
span: Span,
id: hir::HirId,
mutbl: hir::Mutability,
cmt_borrowed: &mc::cmt_<'tcx>,
cmt_borrowed: &mc::Place<'tcx>,
) {
debug!(
"link_region_from_node_type(id={:?}, mutbl={:?}, cmt_borrowed={:?})",
@ -1153,61 +1152,35 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
span: Span,
borrow_region: ty::Region<'tcx>,
borrow_kind: ty::BorrowKind,
borrow_cmt: &mc::cmt_<'tcx>,
borrow_place: &mc::Place<'tcx>,
) {
let origin = infer::DataBorrowed(borrow_cmt.ty, span);
self.type_must_outlive(origin, borrow_cmt.ty, borrow_region);
let origin = infer::DataBorrowed(borrow_place.ty, span);
self.type_must_outlive(origin, borrow_place.ty, borrow_region);
let mut borrow_kind = borrow_kind;
let mut borrow_cmt_cat = borrow_cmt.cat.clone();
loop {
for pointer_ty in borrow_place.deref_tys() {
debug!(
"link_region(borrow_region={:?}, borrow_kind={:?}, borrow_cmt={:?})",
borrow_region, borrow_kind, borrow_cmt
"link_region(borrow_region={:?}, borrow_kind={:?}, pointer_ty={:?})",
borrow_region, borrow_kind, borrow_place
);
match borrow_cmt_cat {
Categorization::Deref(ref_cmt, mc::BorrowedPtr(ref_kind, ref_region)) => {
match self.link_reborrowed_region(
match pointer_ty.kind {
ty::RawPtr(_) => return,
ty::Ref(ref_region, _, ref_mutability) => {
if self.link_reborrowed_region(
span,
borrow_region,
borrow_kind,
ref_cmt,
ref_region,
ref_kind,
borrow_cmt.note,
ref_mutability,
) {
Some((c, k)) => {
borrow_cmt_cat = c.cat.clone();
borrow_kind = k;
}
None => {
return;
}
return;
}
}
Categorization::Downcast(cmt_base, _)
| Categorization::Deref(cmt_base, mc::Unique)
| Categorization::Interior(cmt_base, _) => {
// Borrowing interior or owned data requires the base
// to be valid and borrowable in the same fashion.
borrow_cmt_cat = cmt_base.cat.clone();
borrow_kind = borrow_kind;
}
Categorization::Deref(_, mc::UnsafePtr(..))
| Categorization::StaticItem
| Categorization::Upvar(..)
| Categorization::Local(..)
| Categorization::ThreadLocal
| Categorization::Rvalue => {
// These are all "base cases" with independent lifetimes
// that are not subject to inference
return;
}
_ => assert!(pointer_ty.is_box(), "unexpected built-in deref type {}", pointer_ty)
}
}
if let mc::PlaceBase::Upvar(upvar_id) = borrow_place.base {
self.link_upvar_region(span, borrow_region, upvar_id);
}
}
/// This is the most complicated case: the path being borrowed is
@ -1231,83 +1204,28 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
///
/// Here `bk` stands for some borrow-kind (e.g., `mut`, `uniq`, etc).
///
/// Unfortunately, there are some complications beyond the simple
/// scenario I just painted:
/// There is a complication beyond the simple scenario I just painted: there
/// may in fact be more levels of reborrowing. In the example, I said the
/// borrow was like `&'z *r`, but it might in fact be a borrow like
/// `&'z **q` where `q` has type `&'a &'b mut T`. In that case, we want to
/// ensure that `'z <= 'a` and `'z <= 'b`.
///
/// 1. The reference `r` might in fact be a "by-ref" upvar. In that
/// case, we have two jobs. First, we are inferring whether this reference
/// should be an `&T`, `&mut T`, or `&uniq T` reference, and we must
/// adjust that based on this borrow (e.g., if this is an `&mut` borrow,
/// then `r` must be an `&mut` reference). Second, whenever we link
/// two regions (here, `'z <= 'a`), we supply a *cause*, and in this
/// case we adjust the cause to indicate that the reference being
/// "reborrowed" is itself an upvar. This provides a nicer error message
/// should something go wrong.
/// The return value of this function indicates whether we *don't* need to
/// the recurse to the next reference up.
///
/// 2. There may in fact be more levels of reborrowing. In the
/// example, I said the borrow was like `&'z *r`, but it might
/// in fact be a borrow like `&'z **q` where `q` has type `&'a
/// &'b mut T`. In that case, we want to ensure that `'z <= 'a`
/// and `'z <= 'b`. This is explained more below.
///
/// The return value of this function indicates whether we need to
/// recurse and process `ref_cmt` (see case 2 above).
/// This is explained more below.
fn link_reborrowed_region(
&self,
span: Span,
borrow_region: ty::Region<'tcx>,
borrow_kind: ty::BorrowKind,
ref_cmt: mc::cmt<'tcx>,
ref_region: ty::Region<'tcx>,
mut ref_kind: ty::BorrowKind,
note: mc::Note,
) -> Option<(mc::cmt<'tcx>, ty::BorrowKind)> {
// Possible upvar ID we may need later to create an entry in the
// maybe link map.
// Detect by-ref upvar `x`:
let cause = match note {
mc::NoteUpvarRef(ref upvar_id) => {
match self.tables.borrow().upvar_capture_map.get(upvar_id) {
Some(&ty::UpvarCapture::ByRef(ref upvar_borrow)) => {
// The mutability of the upvar may have been modified
// by the above adjustment, so update our local variable.
ref_kind = upvar_borrow.kind;
infer::ReborrowUpvar(span, *upvar_id)
}
_ => {
span_bug!(span, "Illegal upvar id: {:?}", upvar_id);
}
}
}
mc::NoteClosureEnv(ref upvar_id) => {
// We don't have any mutability changes to propagate, but
// we do want to note that an upvar reborrow caused this
// link
infer::ReborrowUpvar(span, *upvar_id)
}
_ => infer::Reborrow(span),
};
ref_mutability: hir::Mutability,
) -> bool {
debug!(
"link_reborrowed_region: {:?} <= {:?}",
borrow_region, ref_region
);
self.sub_regions(cause, borrow_region, ref_region);
// If we end up needing to recurse and establish a region link
// with `ref_cmt`, calculate what borrow kind we will end up
// needing. This will be used below.
//
// One interesting twist is that we can weaken the borrow kind
// when we recurse: to reborrow an `&mut` referent as mutable,
// borrowck requires a unique path to the `&mut` reference but not
// necessarily a *mutable* path.
let new_borrow_kind = match borrow_kind {
ty::ImmBorrow => ty::ImmBorrow,
ty::MutBorrow | ty::UniqueImmBorrow => ty::UniqueImmBorrow,
};
self.sub_regions(infer::Reborrow(span), borrow_region, ref_region);
// Decide whether we need to recurse and link any regions within
// the `ref_cmt`. This is concerned for the case where the value
@ -1336,24 +1254,83 @@ impl<'a, 'tcx> RegionCtxt<'a, 'tcx> {
// (Note that since we have not examined `ref_cmt.cat`, we don't
// know whether this scenario has occurred; but I wanted to show
// how all the types get adjusted.)
match ref_kind {
ty::ImmBorrow => {
match ref_mutability {
hir::Mutability::Immutable => {
// The reference being reborrowed is a shareable ref of
// type `&'a T`. In this case, it doesn't matter where we
// *found* the `&T` pointer, the memory it references will
// be valid and immutable for `'a`. So we can stop here.
//
// (Note that the `borrow_kind` must also be ImmBorrow or
// else the user is borrowed imm memory as mut memory,
// which means they'll get an error downstream in borrowck
// anyhow.)
return None;
true
}
ty::MutBorrow | ty::UniqueImmBorrow => {
// The reference being reborrowed is either an `&mut T` or
// `&uniq T`. This is the case where recursion is needed.
return Some((ref_cmt, new_borrow_kind));
hir::Mutability::Mutable => {
// The reference being reborrowed is either an `&mut T`. This is
// the case where recursion is needed.
false
}
}
}
/// An upvar may be behind up to 2 references:
///
/// * One can come from the reference to a "by-reference" upvar.
/// * Another one can come from the reference to the closure itself if it's
/// a `FnMut` or `Fn` closure.
///
/// This function links the lifetimes of those references to the lifetime
/// of the borrow that's provided. See [link_reborrowed_region] for some
/// more explanation of this in the general case.
///
/// We also supply a *cause*, and in this case we set the cause to
/// indicate that the reference being "reborrowed" is itself an upvar. This
/// provides a nicer error message should something go wrong.
fn link_upvar_region(
&self,
span: Span,
borrow_region: ty::Region<'tcx>,
upvar_id: ty::UpvarId,
) {
debug!("link_upvar_region(borrorw_region={:?}, upvar_id={:?}", borrow_region, upvar_id);
// A by-reference upvar can't be borrowed for longer than the
// upvar is borrowed from the environment.
match self.tables.borrow().upvar_capture(upvar_id) {
ty::UpvarCapture::ByRef(upvar_borrow) => {
self.sub_regions(
infer::ReborrowUpvar(span, upvar_id),
borrow_region,
upvar_borrow.region,
);
if let ty::ImmBorrow = upvar_borrow.kind {
debug!("link_upvar_region: capture by shared ref");
return;
}
}
ty::UpvarCapture::ByValue => {}
}
let fn_hir_id = self.tcx.hir().local_def_id_to_hir_id(upvar_id.closure_expr_id);
let ty = self.resolve_node_type(fn_hir_id);
debug!("link_upvar_region: ty={:?}", ty);
// A closure capture can't be borrowed for longer than the
// reference to the closure.
if let ty::Closure(closure_def_id, substs) = ty.kind {
match self.infcx.closure_kind(closure_def_id, substs) {
Some(ty::ClosureKind::Fn) | Some(ty::ClosureKind::FnMut) => {
// Region of environment pointer
let env_region = self.tcx.mk_region(ty::ReFree(ty::FreeRegion {
scope: upvar_id.closure_expr_id.to_def_id(),
bound_region: ty::BrEnv
}));
self.sub_regions(
infer::ReborrowUpvar(span, upvar_id),
borrow_region,
env_region,
);
}
Some(ty::ClosureKind::FnOnce) => {}
None => {
span_bug!(span, "Have not inferred closure kind before regionck");
}
}
}
}

View file

@ -32,9 +32,9 @@
use super::FnCtxt;
use crate::middle::expr_use_visitor as euv;
use crate::middle::mem_categorization as mc;
use crate::middle::mem_categorization::Categorization;
use crate::expr_use_visitor as euv;
use crate::mem_categorization as mc;
use crate::mem_categorization::PlaceBase;
use rustc::hir;
use rustc::hir::def_id::DefId;
use rustc::hir::def_id::LocalDefId;
@ -76,6 +76,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InferBorrowKindVisitor<'a, 'tcx> {
}
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// Analysis starting point.
fn analyze_closure(
&self,
closure_hir_id: hir::HirId,
@ -83,9 +84,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
body: &hir::Body,
capture_clause: hir::CaptureBy,
) {
/*!
* Analysis starting point.
*/
debug!(
"analyze_closure(id={:?}, body.id={:?})",
@ -171,20 +169,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let body_owner_def_id = self.tcx.hir().body_owner_def_id(body.id());
assert_eq!(body_owner_def_id, closure_def_id);
let region_scope_tree = &self.tcx.region_scope_tree(body_owner_def_id);
let mut delegate = InferBorrowKind {
fcx: self,
closure_def_id: closure_def_id,
closure_def_id,
current_closure_kind: ty::ClosureKind::LATTICE_BOTTOM,
current_origin: None,
adjust_upvar_captures: ty::UpvarCaptureMap::default(),
};
euv::ExprUseVisitor::with_infer(
euv::ExprUseVisitor::new(
&mut delegate,
&self.infcx,
body_owner_def_id,
self.param_env,
region_scope_tree,
&self.tables.borrow(),
)
.consume_body(body);
@ -312,13 +308,10 @@ struct InferBorrowKind<'a, 'tcx> {
impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
fn adjust_upvar_borrow_kind_for_consume(
&mut self,
cmt: &mc::cmt_<'tcx>,
place: &mc::Place<'tcx>,
mode: euv::ConsumeMode,
) {
debug!(
"adjust_upvar_borrow_kind_for_consume(cmt={:?}, mode={:?})",
cmt, mode
);
debug!("adjust_upvar_borrow_kind_for_consume(place={:?}, mode={:?})", place, mode);
// we only care about moves
match mode {
@ -329,132 +322,68 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
}
let tcx = self.fcx.tcx;
let upvar_id = if let PlaceBase::Upvar(upvar_id) = place.base {
upvar_id
} else {
return;
};
// watch out for a move of the deref of a borrowed pointer;
// for that to be legal, the upvar would have to be borrowed
// by value instead
let guarantor = cmt.guarantor();
debug!(
"adjust_upvar_borrow_kind_for_consume: guarantor={:?}",
guarantor
debug!("adjust_upvar_borrow_kind_for_consume: upvar={:?}", upvar_id);
// To move out of an upvar, this must be a FnOnce closure
self.adjust_closure_kind(
upvar_id.closure_expr_id,
ty::ClosureKind::FnOnce,
place.span,
var_name(tcx, upvar_id.var_path.hir_id),
);
debug!(
"adjust_upvar_borrow_kind_for_consume: guarantor.cat={:?}",
guarantor.cat
);
if let Categorization::Deref(_, mc::BorrowedPtr(..)) = guarantor.cat {
debug!(
"adjust_upvar_borrow_kind_for_consume: found deref with note {:?}",
cmt.note
);
match guarantor.note {
mc::NoteUpvarRef(upvar_id) => {
debug!(
"adjust_upvar_borrow_kind_for_consume: \
setting upvar_id={:?} to by value",
upvar_id
);
// to move out of an upvar, this must be a FnOnce closure
self.adjust_closure_kind(
upvar_id.closure_expr_id,
ty::ClosureKind::FnOnce,
guarantor.span,
var_name(tcx, upvar_id.var_path.hir_id),
);
self.adjust_upvar_captures
.insert(upvar_id, ty::UpvarCapture::ByValue);
}
mc::NoteClosureEnv(upvar_id) => {
// we get just a closureenv ref if this is a
// `move` closure, or if the upvar has already
// been inferred to by-value. In any case, we
// must still adjust the kind of the closure
// to be a FnOnce closure to permit moves out
// of the environment.
self.adjust_closure_kind(
upvar_id.closure_expr_id,
ty::ClosureKind::FnOnce,
guarantor.span,
var_name(tcx, upvar_id.var_path.hir_id),
);
}
mc::NoteIndex | mc::NoteNone => {}
}
}
self.adjust_upvar_captures.insert(upvar_id, ty::UpvarCapture::ByValue);
}
/// Indicates that `cmt` is being directly mutated (e.g., assigned
/// to). If cmt contains any by-ref upvars, this implies that
/// those upvars must be borrowed using an `&mut` borrow.
fn adjust_upvar_borrow_kind_for_mut(&mut self, cmt: &mc::cmt_<'tcx>) {
debug!("adjust_upvar_borrow_kind_for_mut(cmt={:?})", cmt);
/// Indicates that `place` is being directly mutated (e.g., assigned
/// to). If the place is based on a by-ref upvar, this implies that
/// the upvar must be borrowed using an `&mut` borrow.
fn adjust_upvar_borrow_kind_for_mut(&mut self, place: &mc::Place<'tcx>) {
debug!("adjust_upvar_borrow_kind_for_mut(place={:?})", place);
match cmt.cat.clone() {
Categorization::Deref(base, mc::Unique)
| Categorization::Interior(base, _)
| Categorization::Downcast(base, _) => {
// Interior or owned data is mutable if base is
// mutable, so iterate to the base.
self.adjust_upvar_borrow_kind_for_mut(&base);
}
Categorization::Deref(base, mc::BorrowedPtr(..)) => {
if !self.try_adjust_upvar_deref(cmt, ty::MutBorrow) {
if let PlaceBase::Upvar(upvar_id) = place.base {
let mut borrow_kind = ty::MutBorrow;
for pointer_ty in place.deref_tys() {
match pointer_ty.kind {
// Raw pointers don't inherit mutability.
ty::RawPtr(_) => return,
// assignment to deref of an `&mut`
// borrowed pointer implies that the
// pointer itself must be unique, but not
// necessarily *mutable*
self.adjust_upvar_borrow_kind_for_unique(&base);
ty::Ref(.., hir::Mutability::Mutable) => borrow_kind = ty::UniqueImmBorrow,
_ => (),
}
}
self.adjust_upvar_deref(upvar_id, place.span, borrow_kind);
}
}
Categorization::Deref(_, mc::UnsafePtr(..))
| Categorization::StaticItem
| Categorization::ThreadLocal
| Categorization::Rvalue
| Categorization::Local(_)
| Categorization::Upvar(..) => {
fn adjust_upvar_borrow_kind_for_unique(&mut self, place: &mc::Place<'tcx>) {
debug!("adjust_upvar_borrow_kind_for_unique(place={:?})", place);
if let PlaceBase::Upvar(upvar_id) = place.base {
if place.deref_tys().any(ty::TyS::is_unsafe_ptr) {
// Raw pointers don't inherit mutability.
return;
}
// for a borrowed pointer to be unique, its base must be unique
self.adjust_upvar_deref(upvar_id, place.span, ty::UniqueImmBorrow);
}
}
fn adjust_upvar_borrow_kind_for_unique(&mut self, cmt: &mc::cmt_<'tcx>) {
debug!("adjust_upvar_borrow_kind_for_unique(cmt={:?})", cmt);
match cmt.cat.clone() {
Categorization::Deref(base, mc::Unique)
| Categorization::Interior(base, _)
| Categorization::Downcast(base, _) => {
// Interior or owned data is unique if base is
// unique.
self.adjust_upvar_borrow_kind_for_unique(&base);
}
Categorization::Deref(base, mc::BorrowedPtr(..)) => {
if !self.try_adjust_upvar_deref(cmt, ty::UniqueImmBorrow) {
// for a borrowed pointer to be unique, its
// base must be unique
self.adjust_upvar_borrow_kind_for_unique(&base);
}
}
Categorization::Deref(_, mc::UnsafePtr(..))
| Categorization::StaticItem
| Categorization::ThreadLocal
| Categorization::Rvalue
| Categorization::Local(_)
| Categorization::Upvar(..) => {}
}
}
fn try_adjust_upvar_deref(
fn adjust_upvar_deref(
&mut self,
cmt: &mc::cmt_<'tcx>,
upvar_id: ty::UpvarId,
place_span: Span,
borrow_kind: ty::BorrowKind,
) -> bool {
) {
assert!(match borrow_kind {
ty::MutBorrow => true,
ty::UniqueImmBorrow => true,
@ -465,39 +394,19 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
let tcx = self.fcx.tcx;
match cmt.note {
mc::NoteUpvarRef(upvar_id) => {
// if this is an implicit deref of an
// upvar, then we need to modify the
// borrow_kind of the upvar to make sure it
// is inferred to mutable if necessary
self.adjust_upvar_borrow_kind(upvar_id, borrow_kind);
// if this is an implicit deref of an
// upvar, then we need to modify the
// borrow_kind of the upvar to make sure it
// is inferred to mutable if necessary
self.adjust_upvar_borrow_kind(upvar_id, borrow_kind);
// also need to be in an FnMut closure since this is not an ImmBorrow
self.adjust_closure_kind(
upvar_id.closure_expr_id,
ty::ClosureKind::FnMut,
cmt.span,
var_name(tcx, upvar_id.var_path.hir_id),
);
true
}
mc::NoteClosureEnv(upvar_id) => {
// this kind of deref occurs in a `move` closure, or
// for a by-value upvar; in either case, to mutate an
// upvar, we need to be an FnMut closure
self.adjust_closure_kind(
upvar_id.closure_expr_id,
ty::ClosureKind::FnMut,
cmt.span,
var_name(tcx, upvar_id.var_path.hir_id),
);
true
}
mc::NoteIndex | mc::NoteNone => false,
}
// also need to be in an FnMut closure since this is not an ImmBorrow
self.adjust_closure_kind(
upvar_id.closure_expr_id,
ty::ClosureKind::FnMut,
place_span,
var_name(tcx, upvar_id.var_path.hir_id),
);
}
/// We infer the borrow_kind with which to borrow upvars in a stack closure.
@ -509,7 +418,7 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
let upvar_capture = self
.adjust_upvar_captures
.get(&upvar_id)
.cloned()
.copied()
.unwrap_or_else(|| self.fcx.tables.borrow().upvar_capture(upvar_id));
debug!(
"adjust_upvar_borrow_kind(upvar_id={:?}, upvar_capture={:?}, kind={:?})",
@ -586,29 +495,29 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
}
impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
fn consume(&mut self, cmt: &mc::cmt_<'tcx>,mode: euv::ConsumeMode) {
debug!("consume(cmt={:?},mode={:?})", cmt, mode);
self.adjust_upvar_borrow_kind_for_consume(cmt, mode);
fn consume(&mut self, place: &mc::Place<'tcx>,mode: euv::ConsumeMode) {
debug!("consume(place={:?},mode={:?})", place, mode);
self.adjust_upvar_borrow_kind_for_consume(place, mode);
}
fn borrow(&mut self, cmt: &mc::cmt_<'tcx>, bk: ty::BorrowKind) {
debug!("borrow(cmt={:?}, bk={:?})", cmt, bk);
fn borrow(&mut self, place: &mc::Place<'tcx>, bk: ty::BorrowKind) {
debug!("borrow(place={:?}, bk={:?})", place, bk);
match bk {
ty::ImmBorrow => {}
ty::UniqueImmBorrow => {
self.adjust_upvar_borrow_kind_for_unique(cmt);
self.adjust_upvar_borrow_kind_for_unique(place);
}
ty::MutBorrow => {
self.adjust_upvar_borrow_kind_for_mut(cmt);
self.adjust_upvar_borrow_kind_for_mut(place);
}
}
}
fn mutate(&mut self, assignee_cmt: &mc::cmt_<'tcx>) {
debug!("mutate(assignee_cmt={:?})", assignee_cmt);
fn mutate(&mut self, assignee_place: &mc::Place<'tcx>) {
debug!("mutate(assignee_place={:?})", assignee_place);
self.adjust_upvar_borrow_kind_for_mut(assignee_cmt);
self.adjust_upvar_borrow_kind_for_mut(assignee_place);
}
}

View file

@ -5,16 +5,17 @@
pub use self::ConsumeMode::*;
use self::OverloadedCallType::*;
use crate::hir::def::Res;
use crate::hir::def_id::DefId;
use crate::hir::ptr::P;
use crate::infer::InferCtxt;
use crate::middle::mem_categorization as mc;
use crate::middle::region;
use crate::ty::{self, TyCtxt, adjustment};
// Export these here so that Clippy can use them.
pub use mc::{PlaceBase, Place, Projection};
use crate::hir::{self, PatKind};
use std::rc::Rc;
use rustc::hir::{self, PatKind};
use rustc::hir::def::Res;
use rustc::hir::def_id::DefId;
use rustc::hir::ptr::P;
use rustc::infer::InferCtxt;
use rustc::ty::{self, TyCtxt, adjustment};
use crate::mem_categorization as mc;
use syntax_pos::Span;
///////////////////////////////////////////////////////////////////////////
@ -23,15 +24,15 @@ use syntax_pos::Span;
/// This trait defines the callbacks you can expect to receive when
/// employing the ExprUseVisitor.
pub trait Delegate<'tcx> {
// The value found at `cmt` is either copied or moved, depending
// The value found at `place` is either copied or moved, depending
// on mode.
fn consume(&mut self, cmt: &mc::cmt_<'tcx>, mode: ConsumeMode);
fn consume(&mut self, place: &mc::Place<'tcx>, mode: ConsumeMode);
// The value found at `cmt` is being borrowed with kind `bk`.
fn borrow(&mut self, cmt: &mc::cmt_<'tcx>, bk: ty::BorrowKind);
// The value found at `place` is being borrowed with kind `bk`.
fn borrow(&mut self, place: &mc::Place<'tcx>, bk: ty::BorrowKind);
// The path at `cmt` is being assigned to.
fn mutate(&mut self, assignee_cmt: &mc::cmt_<'tcx>);
// The path at `place` is being assigned to.
fn mutate(&mut self, assignee_place: &mc::Place<'tcx>);
}
#[derive(Copy, Clone, PartialEq, Debug)]
@ -85,7 +86,6 @@ impl OverloadedCallType {
pub struct ExprUseVisitor<'a, 'tcx> {
mc: mc::MemCategorizationContext<'a, 'tcx>,
delegate: &'a mut dyn Delegate<'tcx>,
param_env: ty::ParamEnv<'tcx>,
}
// If the MC results in an error, it's because the type check
@ -112,49 +112,22 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
///
/// - `delegate` -- who receives the callbacks
/// - `param_env` --- parameter environment for trait lookups (esp. pertaining to `Copy`)
/// - `region_scope_tree` --- region scope tree for the code being analyzed
/// - `tables` --- typeck results for the code being analyzed
///
/// See also `with_infer`, which is used *during* typeck.
pub fn new(
delegate: &'a mut (dyn Delegate<'tcx> + 'a),
tcx: TyCtxt<'tcx>,
body_owner: DefId,
param_env: ty::ParamEnv<'tcx>,
region_scope_tree: &'a region::ScopeTree,
tables: &'a ty::TypeckTables<'tcx>,
) -> Self {
ExprUseVisitor {
mc: mc::MemCategorizationContext::new(tcx,
param_env,
body_owner,
region_scope_tree,
tables),
delegate,
param_env,
}
}
}
impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
pub fn with_infer(
delegate: &'a mut (dyn Delegate<'tcx> + 'a),
infcx: &'a InferCtxt<'a, 'tcx>,
body_owner: DefId,
param_env: ty::ParamEnv<'tcx>,
region_scope_tree: &'a region::ScopeTree,
tables: &'a ty::TypeckTables<'tcx>,
) -> Self {
ExprUseVisitor {
mc: mc::MemCategorizationContext::with_infer(
mc: mc::MemCategorizationContext::new(
infcx,
param_env,
body_owner,
region_scope_tree,
tables,
),
delegate,
param_env,
}
}
@ -165,26 +138,23 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
let param_ty = return_if_err!(self.mc.pat_ty_adjusted(&param.pat));
debug!("consume_body: param_ty = {:?}", param_ty);
let param_cmt = Rc::new(self.mc.cat_rvalue(
param.hir_id,
param.pat.span,
param_ty));
let param_place = self.mc.cat_rvalue(param.hir_id, param.pat.span, param_ty);
self.walk_irrefutable_pat(param_cmt, &param.pat);
self.walk_irrefutable_pat(&param_place, &param.pat);
}
self.consume_expr(&body.value);
}
fn tcx(&self) -> TyCtxt<'tcx> {
self.mc.tcx
self.mc.tcx()
}
fn delegate_consume(&mut self, cmt: &mc::cmt_<'tcx>) {
debug!("delegate_consume(cmt={:?})", cmt);
fn delegate_consume(&mut self, place: &Place<'tcx>) {
debug!("delegate_consume(place={:?})", place);
let mode = copy_or_move(&self.mc, self.param_env, cmt);
self.delegate.consume(cmt, mode);
let mode = copy_or_move(&self.mc, place);
self.delegate.consume(place, mode);
}
fn consume_exprs(&mut self, exprs: &[hir::Expr]) {
@ -196,22 +166,22 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
pub fn consume_expr(&mut self, expr: &hir::Expr) {
debug!("consume_expr(expr={:?})", expr);
let cmt = return_if_err!(self.mc.cat_expr(expr));
self.delegate_consume(&cmt);
let place = return_if_err!(self.mc.cat_expr(expr));
self.delegate_consume(&place);
self.walk_expr(expr);
}
fn mutate_expr(&mut self, expr: &hir::Expr) {
let cmt = return_if_err!(self.mc.cat_expr(expr));
self.delegate.mutate(&cmt);
let place = return_if_err!(self.mc.cat_expr(expr));
self.delegate.mutate(&place);
self.walk_expr(expr);
}
fn borrow_expr(&mut self, expr: &hir::Expr, bk: ty::BorrowKind) {
debug!("borrow_expr(expr={:?}, bk={:?})", expr, bk);
let cmt = return_if_err!(self.mc.cat_expr(expr));
self.delegate.borrow(&cmt, bk);
let place = return_if_err!(self.mc.cat_expr(expr));
self.delegate.borrow(&place, bk);
self.walk_expr(expr)
}
@ -263,12 +233,12 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
}
hir::ExprKind::Match(ref discr, ref arms, _) => {
let discr_cmt = Rc::new(return_if_err!(self.mc.cat_expr(&discr)));
let discr_place = return_if_err!(self.mc.cat_expr(&discr));
self.borrow_expr(&discr, ty::ImmBorrow);
// treatment of the discriminant is handled while walking the arms.
for arm in arms {
self.walk_arm(discr_cmt.clone(), arm);
self.walk_arm(&discr_place, arm);
}
}
@ -414,8 +384,8 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
// "assigns", which is handled by
// `walk_pat`:
self.walk_expr(&expr);
let init_cmt = Rc::new(return_if_err!(self.mc.cat_expr(&expr)));
self.walk_irrefutable_pat(init_cmt, &local.pat);
let init_place = return_if_err!(self.mc.cat_expr(&expr));
self.walk_irrefutable_pat(&init_place, &local.pat);
}
}
@ -446,11 +416,11 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
None => { return; }
};
let with_cmt = Rc::new(return_if_err!(self.mc.cat_expr(&with_expr)));
let with_place = return_if_err!(self.mc.cat_expr(&with_expr));
// Select just those fields of the `with`
// expression that will actually be used
match with_cmt.ty.kind {
match with_place.ty.kind {
ty::Adt(adt, substs) if adt.is_struct() => {
// Consume those fields of the with expression that are needed.
for (f_index, with_field) in adt.non_enum_variant().fields.iter().enumerate() {
@ -458,14 +428,12 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
self.tcx().field_index(f.hir_id, self.mc.tables) == f_index
});
if !is_mentioned {
let cmt_field = self.mc.cat_field(
let field_place = self.mc.cat_projection(
&*with_expr,
with_cmt.clone(),
f_index,
with_field.ident,
with_field.ty(self.tcx(), substs)
with_place.clone(),
with_field.ty(self.tcx(), substs),
);
self.delegate_consume(&cmt_field);
self.delegate_consume(&field_place);
}
}
}
@ -492,7 +460,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
// process.
fn walk_adjustment(&mut self, expr: &hir::Expr) {
let adjustments = self.mc.tables.expr_adjustments(expr);
let mut cmt = return_if_err!(self.mc.cat_expr_unadjusted(expr));
let mut place = return_if_err!(self.mc.cat_expr_unadjusted(expr));
for adjustment in adjustments {
debug!("walk_adjustment expr={:?} adj={:?}", expr, adjustment);
match adjustment.kind {
@ -500,7 +468,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
adjustment::Adjust::Pointer(_) => {
// Creating a closure/fn-pointer or unsizing consumes
// the input and stores it into the resulting rvalue.
self.delegate_consume(&cmt);
self.delegate_consume(&place);
}
adjustment::Adjust::Deref(None) => {}
@ -512,47 +480,47 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
// this is an autoref of `x`.
adjustment::Adjust::Deref(Some(ref deref)) => {
let bk = ty::BorrowKind::from_mutbl(deref.mutbl);
self.delegate.borrow(&cmt, bk);
self.delegate.borrow(&place, bk);
}
adjustment::Adjust::Borrow(ref autoref) => {
self.walk_autoref(expr, &cmt, autoref);
self.walk_autoref(expr, &place, autoref);
}
}
cmt = return_if_err!(self.mc.cat_expr_adjusted(expr, cmt, &adjustment));
place = return_if_err!(self.mc.cat_expr_adjusted(expr, place, &adjustment));
}
}
/// Walks the autoref `autoref` applied to the autoderef'd
/// `expr`. `cmt_base` is the mem-categorized form of `expr`
/// `expr`. `base_place` is the mem-categorized form of `expr`
/// after all relevant autoderefs have occurred.
fn walk_autoref(&mut self,
expr: &hir::Expr,
cmt_base: &mc::cmt_<'tcx>,
base_place: &mc::Place<'tcx>,
autoref: &adjustment::AutoBorrow<'tcx>) {
debug!("walk_autoref(expr.hir_id={} cmt_base={:?} autoref={:?})",
debug!("walk_autoref(expr.hir_id={} base_place={:?} autoref={:?})",
expr.hir_id,
cmt_base,
base_place,
autoref);
match *autoref {
adjustment::AutoBorrow::Ref(_, m) => {
self.delegate.borrow(cmt_base, ty::BorrowKind::from_mutbl(m.into()));
self.delegate.borrow(base_place, ty::BorrowKind::from_mutbl(m.into()));
}
adjustment::AutoBorrow::RawPtr(m) => {
debug!("walk_autoref: expr.hir_id={} cmt_base={:?}",
debug!("walk_autoref: expr.hir_id={} base_place={:?}",
expr.hir_id,
cmt_base);
base_place);
self.delegate.borrow(cmt_base, ty::BorrowKind::from_mutbl(m));
self.delegate.borrow(base_place, ty::BorrowKind::from_mutbl(m));
}
}
}
fn walk_arm(&mut self, discr_cmt: mc::cmt<'tcx>, arm: &hir::Arm) {
self.walk_pat(discr_cmt.clone(), &arm.pat);
fn walk_arm(&mut self, discr_place: &Place<'tcx>, arm: &hir::Arm) {
self.walk_pat(discr_place, &arm.pat);
if let Some(hir::Guard::If(ref e)) = arm.guard {
self.consume_expr(e)
@ -563,22 +531,22 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
/// Walks a pat that occurs in isolation (i.e., top-level of fn argument or
/// let binding, and *not* a match arm or nested pat.)
fn walk_irrefutable_pat(&mut self, cmt_discr: mc::cmt<'tcx>, pat: &hir::Pat) {
self.walk_pat(cmt_discr, pat);
fn walk_irrefutable_pat(&mut self, discr_place: &Place<'tcx>, pat: &hir::Pat) {
self.walk_pat(discr_place, pat);
}
/// The core driver for walking a pattern
fn walk_pat(&mut self, cmt_discr: mc::cmt<'tcx>, pat: &hir::Pat) {
debug!("walk_pat(cmt_discr={:?}, pat={:?})", cmt_discr, pat);
fn walk_pat(&mut self, discr_place: &Place<'tcx>, pat: &hir::Pat) {
debug!("walk_pat(discr_place={:?}, pat={:?})", discr_place, pat);
let tcx = self.tcx();
let ExprUseVisitor { ref mc, ref mut delegate, param_env } = *self;
return_if_err!(mc.cat_pattern(cmt_discr.clone(), pat, |cmt_pat, pat| {
let ExprUseVisitor { ref mc, ref mut delegate } = *self;
return_if_err!(mc.cat_pattern(discr_place.clone(), pat, |place, pat| {
if let PatKind::Binding(_, canonical_id, ..) = pat.kind {
debug!(
"walk_pat: binding cmt_pat={:?} pat={:?}",
cmt_pat,
"walk_pat: binding place={:?} pat={:?}",
place,
pat,
);
if let Some(&bm) = mc.tables.pat_binding_modes().get(pat.hir_id) {
@ -591,20 +559,20 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
// Each match binding is effectively an assignment to the
// binding being produced.
let def = Res::Local(canonical_id);
if let Ok(ref binding_cmt) = mc.cat_res(pat.hir_id, pat.span, pat_ty, def) {
delegate.mutate(binding_cmt);
if let Ok(ref binding_place) = mc.cat_res(pat.hir_id, pat.span, pat_ty, def) {
delegate.mutate(binding_place);
}
// It is also a borrow or copy/move of the value being matched.
match bm {
ty::BindByReference(m) => {
let bk = ty::BorrowKind::from_mutbl(m);
delegate.borrow(&cmt_pat, bk);
delegate.borrow(place, bk);
}
ty::BindByValue(..) => {
let mode = copy_or_move(mc, param_env, &cmt_pat);
let mode = copy_or_move(mc, place);
debug!("walk_pat binding consuming pat");
delegate.consume(&cmt_pat, mode);
delegate.consume(place, mode);
}
}
} else {
@ -625,16 +593,18 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
closure_expr_id: closure_def_id.to_local(),
};
let upvar_capture = self.mc.tables.upvar_capture(upvar_id);
let cmt_var = return_if_err!(self.cat_captured_var(closure_expr.hir_id,
fn_decl_span,
var_id));
let captured_place = return_if_err!(self.cat_captured_var(
closure_expr.hir_id,
fn_decl_span,
var_id,
));
match upvar_capture {
ty::UpvarCapture::ByValue => {
let mode = copy_or_move(&self.mc, self.param_env, &cmt_var);
self.delegate.consume(&cmt_var, mode);
let mode = copy_or_move(&self.mc, &captured_place);
self.delegate.consume(&captured_place, mode);
}
ty::UpvarCapture::ByRef(upvar_borrow) => {
self.delegate.borrow(&cmt_var, upvar_borrow.kind);
self.delegate.borrow(&captured_place, upvar_borrow.kind);
}
}
}
@ -645,8 +615,8 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
closure_hir_id: hir::HirId,
closure_span: Span,
var_id: hir::HirId)
-> mc::McResult<mc::cmt_<'tcx>> {
// Create the cmt for the variable being borrowed, from the
-> mc::McResult<mc::Place<'tcx>> {
// Create the place for the variable being borrowed, from the
// perspective of the creator (parent) of the closure.
let var_ty = self.mc.node_ty(var_id)?;
self.mc.cat_res(closure_hir_id, closure_span, var_ty, Res::Local(var_id))
@ -655,10 +625,9 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
fn copy_or_move<'a, 'tcx>(
mc: &mc::MemCategorizationContext<'a, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
cmt: &mc::cmt_<'tcx>,
place: &Place<'tcx>,
) -> ConsumeMode {
if !mc.type_is_copy_modulo_regions(param_env, cmt.ty, cmt.span) {
if !mc.type_is_copy_modulo_regions(place.ty, place.span) {
Move
} else {
Copy

View file

@ -75,6 +75,9 @@ This API is completely unstable and subject to change.
#[macro_use] extern crate rustc;
// This is used by Clippy.
pub mod expr_use_visitor;
mod astconv;
mod check;
mod check_unused;
@ -83,6 +86,7 @@ mod collect;
mod constrained_generic_params;
mod structured_errors;
mod impl_wf_check;
mod mem_categorization;
mod namespace;
mod outlives;
mod variance;

View file

@ -0,0 +1,707 @@
//! # Categorization
//!
//! The job of the categorization module is to analyze an expression to
//! determine what kind of memory is used in evaluating it (for example,
//! where dereferences occur and what kind of pointer is dereferenced;
//! whether the memory is mutable, etc.).
//!
//! Categorization effectively transforms all of our expressions into
//! expressions of the following forms (the actual enum has many more
//! possibilities, naturally, but they are all variants of these base
//! forms):
//!
//! E = rvalue // some computed rvalue
//! | x // address of a local variable or argument
//! | *E // deref of a ptr
//! | E.comp // access to an interior component
//!
//! Imagine a routine ToAddr(Expr) that evaluates an expression and returns an
//! address where the result is to be found. If Expr is a place, then this
//! is the address of the place. If `Expr` is an rvalue, this is the address of
//! some temporary spot in memory where the result is stored.
//!
//! Now, `cat_expr()` classifies the expression `Expr` and the address `A = ToAddr(Expr)`
//! as follows:
//!
//! - `cat`: what kind of expression was this? This is a subset of the
//! full expression forms which only includes those that we care about
//! for the purpose of the analysis.
//! - `mutbl`: mutability of the address `A`.
//! - `ty`: the type of data found at the address `A`.
//!
//! The resulting categorization tree differs somewhat from the expressions
//! themselves. For example, auto-derefs are explicit. Also, an index a[b] is
//! decomposed into two operations: a dereference to reach the array data and
//! then an index to jump forward to the relevant item.
//!
//! ## By-reference upvars
//!
//! One part of the codegen which may be non-obvious is that we translate
//! closure upvars into the dereference of a borrowed pointer; this more closely
//! resembles the runtime codegen. So, for example, if we had:
//!
//! let mut x = 3;
//! let y = 5;
//! let inc = || x += y;
//!
//! Then when we categorize `x` (*within* the closure) we would yield a
//! result of `*x'`, effectively, where `x'` is a `Categorization::Upvar` reference
//! tied to `x`. The type of `x'` will be a borrowed pointer.
use rustc::hir;
use rustc::hir::PatKind;
use rustc::hir::def_id::DefId;
use rustc::hir::def::{Res, DefKind};
use rustc::infer::InferCtxt;
use rustc::ty::adjustment;
use rustc::ty::{self, Ty, TyCtxt};
use rustc::ty::fold::TypeFoldable;
use syntax_pos::Span;
use rustc_data_structures::fx::FxIndexMap;
#[derive(Clone, Debug)]
pub enum PlaceBase {
/// A temporary variable
Rvalue,
/// A named `static` item
StaticItem,
/// A named local variable
Local(hir::HirId),
/// An upvar referenced by closure env
Upvar(ty::UpvarId),
}
#[derive(Clone, Debug)]
pub enum Projection<'tcx> {
/// A dereference of a pointer, reference or `Box<T>` of the given type
Deref(Ty<'tcx>),
/// An index or a field
Other,
}
/// A `Place` represents how a value is located in memory.
///
/// This is an HIR version of `mir::Place`
#[derive(Clone, Debug)]
pub struct Place<'tcx> {
/// `HirId` of the expression or pattern producing this value.
pub hir_id: hir::HirId,
/// The `Span` of the expression or pattern producing this value.
pub span: Span,
/// The type of the `Place`
pub ty: Ty<'tcx>,
/// The "outermost" place that holds this value.
pub base: PlaceBase,
/// How this place is derived from the base place.
pub projections: Vec<Projection<'tcx>>,
}
impl<'tcx> Place<'tcx> {
/// Returns an iterator of the types that have to be dereferenced to access
/// the `Place`.
///
/// The types are in the reverse order that they are applied. So if
/// `x: &*const u32` and the `Place` is `**x`, then the types returned are
///`*const u32` then `&*const u32`.
crate fn deref_tys(&self) -> impl Iterator<Item=Ty<'tcx>> + '_ {
self.projections.iter().rev().filter_map(|proj| if let Projection::Deref(deref_ty) = *proj {
Some(deref_ty)
} else {
None
})
}
}
crate trait HirNode {
fn hir_id(&self) -> hir::HirId;
fn span(&self) -> Span;
}
impl HirNode for hir::Expr {
fn hir_id(&self) -> hir::HirId { self.hir_id }
fn span(&self) -> Span { self.span }
}
impl HirNode for hir::Pat {
fn hir_id(&self) -> hir::HirId { self.hir_id }
fn span(&self) -> Span { self.span }
}
#[derive(Clone)]
crate struct MemCategorizationContext<'a, 'tcx> {
crate tables: &'a ty::TypeckTables<'tcx>,
infcx: &'a InferCtxt<'a, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
body_owner: DefId,
upvars: Option<&'tcx FxIndexMap<hir::HirId, hir::Upvar>>,
}
crate type McResult<T> = Result<T, ()>;
impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> {
/// Creates a `MemCategorizationContext`.
crate fn new(
infcx: &'a InferCtxt<'a, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
body_owner: DefId,
tables: &'a ty::TypeckTables<'tcx>,
) -> MemCategorizationContext<'a, 'tcx> {
MemCategorizationContext {
tables,
infcx,
param_env,
body_owner,
upvars: infcx.tcx.upvars(body_owner),
}
}
crate fn tcx(&self) -> TyCtxt<'tcx> {
self.infcx.tcx
}
crate fn type_is_copy_modulo_regions(
&self,
ty: Ty<'tcx>,
span: Span,
) -> bool {
self.infcx.type_is_copy_modulo_regions(self.param_env, ty, span)
}
fn resolve_vars_if_possible<T>(&self, value: &T) -> T
where T: TypeFoldable<'tcx>
{
self.infcx.resolve_vars_if_possible(value)
}
fn is_tainted_by_errors(&self) -> bool {
self.infcx.is_tainted_by_errors()
}
fn resolve_type_vars_or_error(&self,
id: hir::HirId,
ty: Option<Ty<'tcx>>)
-> McResult<Ty<'tcx>> {
match ty {
Some(ty) => {
let ty = self.resolve_vars_if_possible(&ty);
if ty.references_error() || ty.is_ty_var() {
debug!("resolve_type_vars_or_error: error from {:?}", ty);
Err(())
} else {
Ok(ty)
}
}
// FIXME
None if self.is_tainted_by_errors() => Err(()),
None => {
bug!("no type for node {}: {} in mem_categorization",
id, self.tcx().hir().node_to_string(id));
}
}
}
crate fn node_ty(&self, hir_id: hir::HirId) -> McResult<Ty<'tcx>> {
self.resolve_type_vars_or_error(hir_id, self.tables.node_type_opt(hir_id))
}
fn expr_ty(&self, expr: &hir::Expr) -> McResult<Ty<'tcx>> {
self.resolve_type_vars_or_error(expr.hir_id, self.tables.expr_ty_opt(expr))
}
crate fn expr_ty_adjusted(&self, expr: &hir::Expr) -> McResult<Ty<'tcx>> {
self.resolve_type_vars_or_error(expr.hir_id, self.tables.expr_ty_adjusted_opt(expr))
}
/// Returns the type of value that this pattern matches against.
/// Some non-obvious cases:
///
/// - a `ref x` binding matches against a value of type `T` and gives
/// `x` the type `&T`; we return `T`.
/// - a pattern with implicit derefs (thanks to default binding
/// modes #42640) may look like `Some(x)` but in fact have
/// implicit deref patterns attached (e.g., it is really
/// `&Some(x)`). In that case, we return the "outermost" type
/// (e.g., `&Option<T>).
crate fn pat_ty_adjusted(&self, pat: &hir::Pat) -> McResult<Ty<'tcx>> {
// Check for implicit `&` types wrapping the pattern; note
// that these are never attached to binding patterns, so
// actually this is somewhat "disjoint" from the code below
// that aims to account for `ref x`.
if let Some(vec) = self.tables.pat_adjustments().get(pat.hir_id) {
if let Some(first_ty) = vec.first() {
debug!("pat_ty(pat={:?}) found adjusted ty `{:?}`", pat, first_ty);
return Ok(first_ty);
}
}
self.pat_ty_unadjusted(pat)
}
/// Like `pat_ty`, but ignores implicit `&` patterns.
fn pat_ty_unadjusted(&self, pat: &hir::Pat) -> McResult<Ty<'tcx>> {
let base_ty = self.node_ty(pat.hir_id)?;
debug!("pat_ty(pat={:?}) base_ty={:?}", pat, base_ty);
// This code detects whether we are looking at a `ref x`,
// and if so, figures out what the type *being borrowed* is.
let ret_ty = match pat.kind {
PatKind::Binding(..) => {
let bm = *self.tables
.pat_binding_modes()
.get(pat.hir_id)
.expect("missing binding mode");
if let ty::BindByReference(_) = bm {
// a bind-by-ref means that the base_ty will be the type of the ident itself,
// but what we want here is the type of the underlying value being borrowed.
// So peel off one-level, turning the &T into T.
match base_ty.builtin_deref(false) {
Some(t) => t.ty,
None => {
debug!("By-ref binding of non-derefable type {:?}", base_ty);
return Err(());
}
}
} else {
base_ty
}
}
_ => base_ty,
};
debug!("pat_ty(pat={:?}) ret_ty={:?}", pat, ret_ty);
Ok(ret_ty)
}
crate fn cat_expr(&self, expr: &hir::Expr) -> McResult<Place<'tcx>> {
// This recursion helper avoids going through *too many*
// adjustments, since *only* non-overloaded deref recurses.
fn helper<'a, 'tcx>(
mc: &MemCategorizationContext<'a, 'tcx>,
expr: &hir::Expr,
adjustments: &[adjustment::Adjustment<'tcx>],
) -> McResult<Place<'tcx>> {
match adjustments.split_last() {
None => mc.cat_expr_unadjusted(expr),
Some((adjustment, previous)) => {
mc.cat_expr_adjusted_with(expr, || helper(mc, expr, previous), adjustment)
}
}
}
helper(self, expr, self.tables.expr_adjustments(expr))
}
crate fn cat_expr_adjusted(&self, expr: &hir::Expr,
previous: Place<'tcx>,
adjustment: &adjustment::Adjustment<'tcx>)
-> McResult<Place<'tcx>> {
self.cat_expr_adjusted_with(expr, || Ok(previous), adjustment)
}
fn cat_expr_adjusted_with<F>(&self, expr: &hir::Expr,
previous: F,
adjustment: &adjustment::Adjustment<'tcx>)
-> McResult<Place<'tcx>>
where F: FnOnce() -> McResult<Place<'tcx>>
{
debug!("cat_expr_adjusted_with({:?}): {:?}", adjustment, expr);
let target = self.resolve_vars_if_possible(&adjustment.target);
match adjustment.kind {
adjustment::Adjust::Deref(overloaded) => {
// Equivalent to *expr or something similar.
let base = if let Some(deref) = overloaded {
let ref_ty = self.tcx().mk_ref(deref.region, ty::TypeAndMut {
ty: target,
mutbl: deref.mutbl,
});
self.cat_rvalue(expr.hir_id, expr.span, ref_ty)
} else {
previous()?
};
self.cat_deref(expr, base)
}
adjustment::Adjust::NeverToAny |
adjustment::Adjust::Pointer(_) |
adjustment::Adjust::Borrow(_) => {
// Result is an rvalue.
Ok(self.cat_rvalue(expr.hir_id, expr.span, target))
}
}
}
crate fn cat_expr_unadjusted(&self, expr: &hir::Expr) -> McResult<Place<'tcx>> {
debug!("cat_expr: id={} expr={:?}", expr.hir_id, expr);
let expr_ty = self.expr_ty(expr)?;
match expr.kind {
hir::ExprKind::Unary(hir::UnDeref, ref e_base) => {
if self.tables.is_method_call(expr) {
self.cat_overloaded_place(expr, e_base)
} else {
let base = self.cat_expr(&e_base)?;
self.cat_deref(expr, base)
}
}
hir::ExprKind::Field(ref base, _) => {
let base = self.cat_expr(&base)?;
debug!("cat_expr(cat_field): id={} expr={:?} base={:?}",
expr.hir_id,
expr,
base);
Ok(self.cat_projection(expr, base, expr_ty))
}
hir::ExprKind::Index(ref base, _) => {
if self.tables.is_method_call(expr) {
// If this is an index implemented by a method call, then it
// will include an implicit deref of the result.
// The call to index() returns a `&T` value, which
// is an rvalue. That is what we will be
// dereferencing.
self.cat_overloaded_place(expr, base)
} else {
let base = self.cat_expr(&base)?;
Ok(self.cat_projection(expr, base, expr_ty))
}
}
hir::ExprKind::Path(ref qpath) => {
let res = self.tables.qpath_res(qpath, expr.hir_id);
self.cat_res(expr.hir_id, expr.span, expr_ty, res)
}
hir::ExprKind::Type(ref e, _) => {
self.cat_expr(&e)
}
hir::ExprKind::AddrOf(..) | hir::ExprKind::Call(..) |
hir::ExprKind::Assign(..) | hir::ExprKind::AssignOp(..) |
hir::ExprKind::Closure(..) | hir::ExprKind::Ret(..) |
hir::ExprKind::Unary(..) | hir::ExprKind::Yield(..) |
hir::ExprKind::MethodCall(..) | hir::ExprKind::Cast(..) | hir::ExprKind::DropTemps(..) |
hir::ExprKind::Array(..) | hir::ExprKind::Tup(..) |
hir::ExprKind::Binary(..) |
hir::ExprKind::Block(..) | hir::ExprKind::Loop(..) | hir::ExprKind::Match(..) |
hir::ExprKind::Lit(..) | hir::ExprKind::Break(..) |
hir::ExprKind::Continue(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) |
hir::ExprKind::InlineAsm(..) | hir::ExprKind::Box(..) | hir::ExprKind::Err => {
Ok(self.cat_rvalue(expr.hir_id, expr.span, expr_ty))
}
}
}
crate fn cat_res(&self,
hir_id: hir::HirId,
span: Span,
expr_ty: Ty<'tcx>,
res: Res)
-> McResult<Place<'tcx>> {
debug!("cat_res: id={:?} expr={:?} def={:?}",
hir_id, expr_ty, res);
match res {
Res::Def(DefKind::Ctor(..), _)
| Res::Def(DefKind::Const, _)
| Res::Def(DefKind::ConstParam, _)
| Res::Def(DefKind::AssocConst, _)
| Res::Def(DefKind::Fn, _)
| Res::Def(DefKind::Method, _)
| Res::SelfCtor(..) => {
Ok(self.cat_rvalue(hir_id, span, expr_ty))
}
Res::Def(DefKind::Static, _) => {
Ok(Place {
hir_id,
span,
ty: expr_ty,
base: PlaceBase::StaticItem,
projections: Vec::new(),
})
}
Res::Local(var_id) => {
if self.upvars.map_or(false, |upvars| upvars.contains_key(&var_id)) {
self.cat_upvar(hir_id, span, var_id)
} else {
Ok(Place {
hir_id,
span,
ty: expr_ty,
base: PlaceBase::Local(var_id),
projections: Vec::new(),
})
}
}
def => span_bug!(span, "unexpected definition in memory categorization: {:?}", def)
}
}
/// Categorize an upvar.
///
/// Note: the actual upvar access contains invisible derefs of closure
/// environment and upvar reference as appropriate. Only regionck cares
/// about these dereferences, so we let it compute them as needed.
fn cat_upvar(
&self,
hir_id: hir::HirId,
span: Span,
var_id: hir::HirId,
) -> McResult<Place<'tcx>> {
let closure_expr_def_id = self.body_owner;
let upvar_id = ty::UpvarId {
var_path: ty::UpvarPath { hir_id: var_id },
closure_expr_id: closure_expr_def_id.to_local(),
};
let var_ty = self.node_ty(var_id)?;
let ret = Place {
hir_id,
span,
ty: var_ty,
base: PlaceBase::Upvar(upvar_id),
projections: Vec::new(),
};
debug!("cat_upvar ret={:?}", ret);
Ok(ret)
}
crate fn cat_rvalue(&self, hir_id: hir::HirId, span: Span, expr_ty: Ty<'tcx>) -> Place<'tcx> {
debug!("cat_rvalue hir_id={:?}, expr_ty={:?}, span={:?}", hir_id, expr_ty, span);
let ret = Place {
hir_id,
span,
base: PlaceBase::Rvalue,
projections: Vec::new(),
ty: expr_ty,
};
debug!("cat_rvalue ret={:?}", ret);
ret
}
crate fn cat_projection<N: HirNode>(
&self,
node: &N,
base_place: Place<'tcx>,
ty: Ty<'tcx>,
) -> Place<'tcx> {
let mut projections = base_place.projections;
projections.push(Projection::Other);
let ret = Place {
hir_id: node.hir_id(),
span: node.span(),
ty,
base: base_place.base,
projections,
};
debug!("cat_field ret {:?}", ret);
ret
}
fn cat_overloaded_place(
&self,
expr: &hir::Expr,
base: &hir::Expr,
) -> McResult<Place<'tcx>> {
debug!("cat_overloaded_place(expr={:?}, base={:?})", expr, base);
// Reconstruct the output assuming it's a reference with the
// same region and mutability as the receiver. This holds for
// `Deref(Mut)::Deref(_mut)` and `Index(Mut)::index(_mut)`.
let place_ty = self.expr_ty(expr)?;
let base_ty = self.expr_ty_adjusted(base)?;
let (region, mutbl) = match base_ty.kind {
ty::Ref(region, _, mutbl) => (region, mutbl),
_ => span_bug!(expr.span, "cat_overloaded_place: base is not a reference")
};
let ref_ty = self.tcx().mk_ref(region, ty::TypeAndMut {
ty: place_ty,
mutbl,
});
let base = self.cat_rvalue(expr.hir_id, expr.span, ref_ty);
self.cat_deref(expr, base)
}
fn cat_deref(
&self,
node: &impl HirNode,
base_place: Place<'tcx>,
) -> McResult<Place<'tcx>> {
debug!("cat_deref: base_place={:?}", base_place);
let base_ty = base_place.ty;
let deref_ty = match base_ty.builtin_deref(true) {
Some(mt) => mt.ty,
None => {
debug!("explicit deref of non-derefable type: {:?}", base_ty);
return Err(());
}
};
let mut projections = base_place.projections;
projections.push(Projection::Deref(base_ty));
let ret = Place {
hir_id: node.hir_id(),
span: node.span(),
ty: deref_ty,
base: base_place.base,
projections,
};
debug!("cat_deref ret {:?}", ret);
Ok(ret)
}
crate fn cat_pattern<F>(&self, place: Place<'tcx>, pat: &hir::Pat, mut op: F) -> McResult<()>
where F: FnMut(&Place<'tcx>, &hir::Pat),
{
self.cat_pattern_(place, pat, &mut op)
}
// FIXME(#19596) This is a workaround, but there should be a better way to do this
fn cat_pattern_<F>(&self, mut place: Place<'tcx>, pat: &hir::Pat, op: &mut F) -> McResult<()>
where F: FnMut(&Place<'tcx>, &hir::Pat)
{
// Here, `place` is the `Place` being matched and pat is the pattern it
// is being matched against.
//
// In general, the way that this works is that we walk down the pattern,
// constructing a `Place` that represents the path that will be taken
// to reach the value being matched.
debug!("cat_pattern(pat={:?}, place={:?})", pat, place);
// If (pattern) adjustments are active for this pattern, adjust the `Place` correspondingly.
// `Place`s are constructed differently from patterns. For example, in
//
// ```
// match foo {
// &&Some(x, ) => { ... },
// _ => { ... },
// }
// ```
//
// the pattern `&&Some(x,)` is represented as `Ref { Ref { TupleStruct }}`. To build the
// corresponding `Place` we start with the `Place` for `foo`, and then, by traversing the
// pattern, try to answer the question: given the address of `foo`, how is `x` reached?
//
// `&&Some(x,)` `place_foo`
// `&Some(x,)` `deref { place_foo}`
// `Some(x,)` `deref { deref { place_foo }}`
// (x,)` `field0 { deref { deref { place_foo }}}` <- resulting place
//
// The above example has no adjustments. If the code were instead the (after adjustments,
// equivalent) version
//
// ```
// match foo {
// Some(x, ) => { ... },
// _ => { ... },
// }
// ```
//
// Then we see that to get the same result, we must start with
// `deref { deref { place_foo }}` instead of `place_foo` since the pattern is now `Some(x,)`
// and not `&&Some(x,)`, even though its assigned type is that of `&&Some(x,)`.
for _ in 0..self.tables
.pat_adjustments()
.get(pat.hir_id)
.map(|v| v.len())
.unwrap_or(0)
{
debug!("cat_pattern: applying adjustment to place={:?}", place);
place = self.cat_deref(pat, place)?;
}
let place = place; // lose mutability
debug!("cat_pattern: applied adjustment derefs to get place={:?}", place);
// Invoke the callback, but only now, after the `place` has adjusted.
//
// To see that this makes sense, consider `match &Some(3) { Some(x) => { ... }}`. In that
// case, the initial `place` will be that for `&Some(3)` and the pattern is `Some(x)`. We
// don't want to call `op` with these incompatible values. As written, what happens instead
// is that `op` is called with the adjusted place (that for `*&Some(3)`) and the pattern
// `Some(x)` (which matches). Recursing once more, `*&Some(3)` and the pattern `Some(x)`
// result in the place `Downcast<Some>(*&Some(3)).0` associated to `x` and invoke `op` with
// that (where the `ref` on `x` is implied).
op(&place, pat);
match pat.kind {
PatKind::TupleStruct(_, ref subpats, _)
| PatKind::Tuple(ref subpats, _) => {
// S(p1, ..., pN) or (p1, ..., pN)
for subpat in subpats.iter() {
let subpat_ty = self.pat_ty_adjusted(&subpat)?;
let sub_place = self.cat_projection(pat, place.clone(), subpat_ty);
self.cat_pattern_(sub_place, &subpat, op)?;
}
}
PatKind::Struct(_, ref field_pats, _) => {
// S { f1: p1, ..., fN: pN }
for fp in field_pats {
let field_ty = self.pat_ty_adjusted(&fp.pat)?;
let field_place = self.cat_projection(pat, place.clone(), field_ty);
self.cat_pattern_(field_place, &fp.pat, op)?;
}
}
PatKind::Or(ref pats) => {
for pat in pats {
self.cat_pattern_(place.clone(), &pat, op)?;
}
}
PatKind::Binding(.., Some(ref subpat)) => {
self.cat_pattern_(place, &subpat, op)?;
}
PatKind::Box(ref subpat) | PatKind::Ref(ref subpat, _) => {
// box p1, &p1, &mut p1. we can ignore the mutability of
// PatKind::Ref since that information is already contained
// in the type.
let subplace = self.cat_deref(pat, place)?;
self.cat_pattern_(subplace, &subpat, op)?;
}
PatKind::Slice(ref before, ref slice, ref after) => {
let element_ty = match place.ty.builtin_index() {
Some(ty) => ty,
None => {
debug!("explicit index of non-indexable type {:?}", place);
return Err(());
}
};
let elt_place = self.cat_projection(pat, place.clone(), element_ty);
for before_pat in before {
self.cat_pattern_(elt_place.clone(), &before_pat, op)?;
}
if let Some(ref slice_pat) = *slice {
let slice_pat_ty = self.pat_ty_adjusted(&slice_pat)?;
let slice_place = self.cat_projection(pat, place, slice_pat_ty);
self.cat_pattern_(slice_place, &slice_pat, op)?;
}
for after_pat in after {
self.cat_pattern_(elt_place.clone(), &after_pat, op)?;
}
}
PatKind::Path(_) | PatKind::Binding(.., None) |
PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild => {
// always ok
}
}
Ok(())
}
}

View file

@ -4,8 +4,7 @@ fn id<T>(t: T) -> T { t }
fn f<'r, T>(v: &'r T) -> Box<dyn FnMut() -> T + 'r> {
id(Box::new(|| *v))
//~^ ERROR E0373
//~| ERROR E0507
//~^ ERROR E0507
}
fn main() {

View file

@ -6,25 +6,6 @@ LL | fn f<'r, T>(v: &'r T) -> Box<dyn FnMut() -> T + 'r> {
LL | id(Box::new(|| *v))
| ^^ move occurs because `*v` has type `T`, which does not implement the `Copy` trait
error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
--> $DIR/issue-4335.rs:6:17
|
LL | id(Box::new(|| *v))
| ^^ - `v` is borrowed here
| |
| may outlive borrowed value `v`
|
note: closure is returned here
--> $DIR/issue-4335.rs:6:5
|
LL | id(Box::new(|| *v))
| ^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
|
LL | id(Box::new(move || *v))
| ^^^^^^^
error: aborting due to previous error
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0373, E0507.
For more information about an error, try `rustc --explain E0373`.
For more information about this error, try `rustc --explain E0507`.

View file

@ -9,7 +9,7 @@ note: first, the lifetime cannot outlive the lifetime `'_` as defined on the bod
|
LL | let _f = || {
| ^^
note: ...so that reference does not outlive borrowed content
note: ...so that closure can access `self`
--> $DIR/regions-addr-of-upvar-self.rs:10:41
|
LL | let p: &'static mut usize = &mut self.food;

View file

@ -2,7 +2,7 @@ error[E0525]: expected a closure that implements the `Fn` trait, but this closur
--> $DIR/unboxed-closures-infer-fn-once-move-from-projection.rs:14:13
|
LL | let c = || drop(y.0);
| ^^^^^^^^-^^^
| ^^^^^^^^---^
| | |
| | closure is `FnOnce` because it moves the variable `y` out of its environment
| this closure implements `FnOnce`, not `Fn`