1
Fork 0

Add comments with examples and tests

This commit is contained in:
Roxane 2021-02-25 18:03:41 -05:00
parent fb3b77a8c8
commit 22eaffe71a
32 changed files with 788 additions and 136 deletions

View file

@ -431,7 +431,29 @@ pub struct TypeckResults<'tcx> {
/// see `MinCaptureInformationMap` for more details.
pub closure_min_captures: ty::MinCaptureInformationMap<'tcx>,
pub closure_fake_reads: FxHashMap<DefId, Vec<(HirPlace<'tcx>, FakeReadCause)>>,
/// Tracks the fake reads required for a closure and the reason for the fake read.
/// When performing pattern matching for closures, there are times we don't end up
/// reading places that are mentioned in a closure (because of _ patterns). However,
/// to ensure the places are initialized, we introduce fake reads.
/// Consider these two examples:
/// ``` (discriminant matching with only wildcard arm)
/// let x: u8;
/// let c = || match x { _ => () };
/// ```
/// In this example, we don't need to actually read/borrow `x` in `c`, and so we don't
/// want to capture it. However, we do still want an error here, because `x` should have
/// to be initialized at the point where c is created. Therefore, we add a "fake read"
/// instead.
/// ``` (destructured assignments)
/// let c = || {
/// let (t1, t2) = t;
/// }
/// ```
/// In the second example, we capture the disjoint fields of `t` (`t.0` & `t.1`), but
/// we never capture `t`. This becomes an issue when we build MIR as we require
/// information on `t` in order to create place `t.0` and `t.1`. We can solve this
/// issue by fake reading `t`.
pub closure_fake_reads: FxHashMap<DefId, Vec<(HirPlace<'tcx>, FakeReadCause, hir::HirId)>>,
/// Stores the type, expression, span and optional scope span of all types
/// that are live across the yield of this generator (if a generator).

View file

@ -90,15 +90,13 @@ fn convert_to_hir_projections_and_truncate_for_capture<'tcx>(
let hir_projection = match mir_projection {
ProjectionElem::Deref => HirProjectionKind::Deref,
ProjectionElem::Field(field, _) => {
// We will never encouter this for multivariant enums,
// read the comment for `Downcast`.
let variant = variant.unwrap_or(VariantIdx::new(0));
HirProjectionKind::Field(field.index() as u32, variant)
}
ProjectionElem::Downcast(.., idx) => {
// This projections exist for enums that have
// single and multiple variants.
// For single variants, enums are not captured completely.
// We don't expect to see multi-variant enums here, as earlier
// phases will have truncated them already. However, there can
// still be downcasts, thanks to single-variant enums.
// We keep track of VariantIdx so we can use this information
// if the next ProjectionElem is a Field.
variant = Some(*idx);
@ -200,7 +198,7 @@ fn find_capture_matching_projections<'a, 'tcx>(
/// Takes a PlaceBuilder and resolves the upvar (if any) within it, so that the
/// `PlaceBuilder` now starts from `PlaceBase::Local`.
///
/// Returns a Result with the error being the HirId of the Upvar that was not found.
/// Returns a Result with the error being the PlaceBuilder (`from_builder`) that was not found.
fn to_upvars_resolved_place_builder<'a, 'tcx>(
from_builder: PlaceBuilder<'tcx>,
tcx: TyCtxt<'tcx>,
@ -305,15 +303,23 @@ impl<'tcx> PlaceBuilder<'tcx> {
to_upvars_resolved_place_builder(self, tcx, typeck_results).unwrap()
}
/// Attempts to resolve the `PlaceBuilder`.
/// On success, it will return the resolved `PlaceBuilder`.
/// On failure, it will return itself.
///
/// Upvars resolve may fail for a `PlaceBuilder` when attempting to
/// resolve a disjoint field whose root variable is not captured
/// (destructured assignments) or when attempting to resolve a root
/// variable (discriminant matching with only wildcard arm) that is
/// not captured. This can happen because the final mir that will be
/// generated doesn't require a read for this place. Failures will only
/// happen inside closures.
crate fn try_upvars_resolved<'a>(
self,
tcx: TyCtxt<'tcx>,
typeck_results: &'a ty::TypeckResults<'tcx>,
) -> Result<PlaceBuilder<'tcx>, PlaceBuilder<'tcx>> {
match to_upvars_resolved_place_builder(self, tcx, typeck_results) {
Ok(upvars_resolved) => Ok(upvars_resolved),
Err(upvars_unresolved) => Err(upvars_unresolved),
}
to_upvars_resolved_place_builder(self, tcx, typeck_results)
}
crate fn base(&self) -> PlaceBase {
@ -662,7 +668,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block,
source_info,
len,
Rvalue::Len(slice.clone().into_place(self.tcx, self.typeck_results)),
Rvalue::Len(slice.into_place(self.tcx, self.typeck_results)),
);
// lt = idx < len
self.cfg.push_assign(

View file

@ -165,13 +165,42 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block.and(Rvalue::Aggregate(box AggregateKind::Tuple, fields))
}
ExprKind::Closure {
closure_id,
substs,
upvars,
movability,
fake_reads: opt_fake_reads,
} => {
ExprKind::Closure { closure_id, substs, upvars, movability, fake_reads } => {
// Convert the closure fake reads, if any, from `ExprRef` to mir `Place`
// and push the fake reads.
// This must come before creating the operands. This is required in case
// there is a fake read and a borrow of the same path, since otherwise the
// fake read might interfere with the borrow. Consider an example like this
// one:
// ```
// let mut x = 0;
// let c = || {
// &mut x; // mutable borrow of `x`
// match x { _ => () } // fake read of `x`
// };
// ```
// FIXME(RFC2229): Remove feature gate once diagnostics are improved
if this.tcx.features().capture_disjoint_fields {
for (thir_place, cause, hir_id) in fake_reads.into_iter() {
let place_builder =
unpack!(block = this.as_place_builder(block, thir_place));
if let Ok(place_builder_resolved) =
place_builder.try_upvars_resolved(this.tcx, this.typeck_results)
{
let mir_place =
place_builder_resolved.into_place(this.tcx, this.typeck_results);
this.cfg.push_fake_read(
block,
this.source_info(this.tcx.hir().span(hir_id)),
cause,
mir_place,
);
}
}
}
// see (*) above
let operands: Vec<_> = upvars
.into_iter()
@ -210,21 +239,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
})
.collect();
if let Some(fake_reads) = opt_fake_reads {
for (thir_place, cause) in fake_reads.into_iter() {
let place_builder =
unpack!(block = this.as_place_builder(block, thir_place));
if let Ok(place_builder_resolved) =
place_builder.clone().try_upvars_resolved(this.tcx, this.typeck_results)
{
let mir_place = place_builder_resolved
.clone()
.into_place(this.tcx, this.typeck_results);
this.cfg.push_fake_read(block, source_info, cause, mir_place);
}
}
}
let result = match substs {
UpvarSubsts::Generator(substs) => {

View file

@ -146,8 +146,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
if let Ok(scrutinee_builder) =
scrutinee_place_builder.clone().try_upvars_resolved(self.tcx, self.typeck_results)
{
let scrutinee_place =
scrutinee_builder.clone().into_place(self.tcx, self.typeck_results);
let scrutinee_place = scrutinee_builder.into_place(self.tcx, self.typeck_results);
self.cfg.push_fake_read(block, source_info, cause_matched_place, scrutinee_place);
}
@ -245,7 +244,19 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let arm_source_info = self.source_info(arm.span);
let arm_scope = (arm.scope, arm_source_info);
self.in_scope(arm_scope, arm.lint_level, |this| {
let body = arm.body.clone();
let body = arm.body;
// `try_upvars_resolved` may fail if it is unable to resolve the given
// `PlaceBuilder` inside a closure. In this case, we don't want to include
// a scrutinee place. `scrutinee_place_builder` will fail to be resolved
// if the only match arm is a wildcard (`_`).
// Example:
// ```
// let foo = (0, 1);
// let c = || {
// match foo { _ => () };
// };
// ```
let mut opt_scrutinee_place: Option<(Option<&Place<'tcx>>, Span)> = None;
let scrutinee_place: Place<'tcx>;
if let Ok(scrutinee_builder) = scrutinee_place_builder
@ -496,6 +507,20 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
VarBindingForm { opt_match_place: Some((ref mut match_place, _)), .. },
)))) = self.local_decls[local].local_info
{
// `try_upvars_resolved` may fail if it is unable to resolve the given
// `PlaceBuilder` inside a closure. In this case, we don't want to include
// a scrutinee place. `scrutinee_place_builder` will fail for destructured
// assignments. This is because a closure only captures the precise places
// that it will read and as a result a closure may not capture the entire
// tuple/struct and rather have individual places that will be read in the
// final MIR.
// Example:
// ```
// let foo = (0, 1);
// let c = || {
// let (v1, v2) = foo;
// };
// ```
if let Ok(match_pair_resolved) =
initializer.clone().try_upvars_resolved(self.tcx, self.typeck_results)
{

View file

@ -160,7 +160,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
if let Ok(test_place_builder) =
place_builder.clone().try_upvars_resolved(self.tcx, self.typeck_results)
{
place = test_place_builder.clone().into_place(self.tcx, self.typeck_results);
place = test_place_builder.into_place(self.tcx, self.typeck_results);
} else {
return;
}

View file

@ -454,20 +454,20 @@ impl<'thir, 'tcx> Cx<'thir, 'tcx> {
.map(|(captured_place, ty)| self.capture_upvar(expr, captured_place, ty)),
);
// Convert the closure fake reads, if any, from hir `Place` to ExprRef
let fake_reads = match self.typeck_results().closure_fake_reads.get(&def_id) {
Some(vals) => Some(
vals.iter()
.map(|(place, cause)| {
(
self.arena.alloc(
self.convert_captured_hir_place(expr, place.clone()),
),
*cause,
)
})
.collect(),
),
None => None,
Some(fake_reads) => fake_reads
.iter()
.map(|(place, cause, hir_id)| {
(
self.arena
.alloc(self.convert_captured_hir_place(expr, place.clone())),
*cause,
*hir_id,
)
})
.collect(),
None => Vec::new(),
};
ExprKind::Closure {

View file

@ -281,7 +281,7 @@ pub enum ExprKind<'thir, 'tcx> {
substs: UpvarSubsts<'tcx>,
upvars: &'thir [Expr<'thir, 'tcx>],
movability: Option<hir::Movability>,
fake_reads: Option<Vec<(&'thir mut Expr<'thir, 'tcx>, FakeReadCause)>>,
fake_reads: Vec<(&'thir mut Expr<'thir, 'tcx>, FakeReadCause, hir::HirId)>,
},
Literal {
literal: &'tcx Const<'tcx>,

View file

@ -248,8 +248,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let final_tupled_upvars_type = self.tcx.mk_tup(final_upvar_tys.iter());
self.demand_suptype(span, substs.tupled_upvars_ty(), final_tupled_upvars_type);
let fake_reads =
delegate.fake_reads.into_iter().map(|(place, cause)| (place, cause)).collect();
let fake_reads = delegate
.fake_reads
.into_iter()
.map(|(place, cause, hir_id)| (place, cause, hir_id))
.collect();
self.typeck_results.borrow_mut().closure_fake_reads.insert(closure_def_id, fake_reads);
// If we are also inferred the closure kind here,
@ -1154,7 +1157,7 @@ struct InferBorrowKind<'a, 'tcx> {
/// Place { V1, [ProjectionKind::Field(Index=1, Variant=0)] } : CaptureKind { E2, MutableBorrow }
/// ```
capture_information: InferredCaptureInformation<'tcx>,
fake_reads: Vec<(Place<'tcx>, FakeReadCause)>,
fake_reads: Vec<(Place<'tcx>, FakeReadCause, hir::HirId)>,
}
impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
@ -1416,9 +1419,9 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
}
impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
fn fake_read(&mut self, place: Place<'tcx>, cause: FakeReadCause) {
fn fake_read(&mut self, place: Place<'tcx>, cause: FakeReadCause, diag_expr_id: hir::HirId) {
if let PlaceBase::Upvar(_) = place.base {
self.fake_reads.push((place, cause));
self.fake_reads.push((place, cause, diag_expr_id));
}
}

View file

@ -371,18 +371,18 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
fn visit_fake_reads_map(&mut self) {
let mut resolved_closure_fake_reads: FxHashMap<
DefId,
Vec<(HirPlace<'tcx>, FakeReadCause)>,
Vec<(HirPlace<'tcx>, FakeReadCause, hir::HirId)>,
> = Default::default();
for (closure_def_id, fake_reads) in
self.fcx.typeck_results.borrow().closure_fake_reads.iter()
{
let mut resolved_fake_reads = Vec::<(HirPlace<'tcx>, FakeReadCause)>::new();
for (place, cause) in fake_reads.iter() {
let mut resolved_fake_reads = Vec::<(HirPlace<'tcx>, FakeReadCause, hir::HirId)>::new();
for (place, cause, hir_id) in fake_reads.iter() {
let locatable =
self.tcx().hir().local_def_id_to_hir_id(closure_def_id.expect_local());
let resolved_fake_read = self.resolve(place.clone(), &locatable);
resolved_fake_reads.push((resolved_fake_read, *cause));
resolved_fake_reads.push((resolved_fake_read, *cause, *hir_id));
}
resolved_closure_fake_reads.insert(*closure_def_id, resolved_fake_reads);
}

View file

@ -7,11 +7,11 @@ pub use self::ConsumeMode::*;
// Export these here so that Clippy can use them.
pub use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, Projection};
use rustc_data_structures::fx::FxIndexMap;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::PatKind;
//use rustc_hir::QPath;
use rustc_index::vec::Idx;
use rustc_infer::infer::InferCtxt;
use rustc_middle::hir::place::ProjectionKind;
@ -54,7 +54,8 @@ pub trait Delegate<'tcx> {
// `diag_expr_id` is the id used for diagnostics (see `consume` for more details).
fn mutate(&mut self, assignee_place: &PlaceWithHirId<'tcx>, diag_expr_id: hir::HirId);
fn fake_read(&mut self, place: Place<'tcx>, cause: FakeReadCause);
// The `place` should be a fake read because of specified `cause`.
fn fake_read(&mut self, place: Place<'tcx>, cause: FakeReadCause, diag_expr_id: hir::HirId);
}
#[derive(Copy, Clone, PartialEq, Debug)]
@ -241,20 +242,33 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
let ExprUseVisitor { ref mc, body_owner: _, delegate: _ } = *self;
let mut needs_to_be_read = false;
for arm in arms.iter() {
return_if_err!(mc.cat_pattern(discr_place.clone(), &arm.pat, |_place, pat| {
return_if_err!(mc.cat_pattern(discr_place.clone(), &arm.pat, |place, pat| {
match &pat.kind {
PatKind::Binding(_, _, _, opt_sub_pat) => {
PatKind::Binding(.., opt_sub_pat) => {
// If the opt_sub_pat is None, than the binding does not count as
// a wildcard for the purpose of borrowing discr
if let None = opt_sub_pat {
// a wildcard for the purpose of borrowing discr.
if opt_sub_pat.is_none() {
needs_to_be_read = true;
}
}
PatKind::TupleStruct(_, _, _)
| PatKind::Struct(_, _, _)
| PatKind::Lit(_) => {
// If the PatKind is a TupleStruct, Struct, or Lit then we want
// to borrow discr
PatKind::TupleStruct(..)
| PatKind::Path(..)
| PatKind::Struct(..)
| PatKind::Tuple(..) => {
// If the PatKind is a TupleStruct, Struct or Tuple then we want to check
// whether the Variant is a MultiVariant or a SingleVariant. We only want
// to borrow discr if it is a MultiVariant.
// If it is a SingleVariant and creates a binding we will handle that when
// this callback gets called again.
if let ty::Adt(def, _) = place.place.base_ty.kind() {
if def.variants.len() > 1 {
needs_to_be_read = true;
}
}
}
PatKind::Lit(_) => {
// If the PatKind is a Lit then we want
// to borrow discr.
needs_to_be_read = true;
}
_ => {}
@ -264,6 +278,16 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
if needs_to_be_read {
self.borrow_expr(&discr, ty::ImmBorrow);
} else {
self.delegate.fake_read(
discr_place.place.clone(),
FakeReadCause::ForMatchedPlace,
discr_place.hir_id,
);
// We always want to walk the discriminant. We want to make sure, for instance,
// that the discriminant has been initialized.
self.walk_expr(&discr);
}
// treatment of the discriminant is handled while walking the arms.
@ -553,7 +577,11 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
}
fn walk_arm(&mut self, discr_place: &PlaceWithHirId<'tcx>, arm: &hir::Arm<'_>) {
self.delegate.fake_read(discr_place.place.clone(), FakeReadCause::ForMatchedPlace);
self.delegate.fake_read(
discr_place.place.clone(),
FakeReadCause::ForMatchedPlace,
discr_place.hir_id,
);
self.walk_pat(discr_place, &arm.pat);
if let Some(hir::Guard::If(ref e)) = arm.guard {
@ -566,7 +594,11 @@ 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, discr_place: &PlaceWithHirId<'tcx>, pat: &hir::Pat<'_>) {
self.delegate.fake_read(discr_place.place.clone(), FakeReadCause::ForLet);
self.delegate.fake_read(
discr_place.place.clone(),
FakeReadCause::ForLet,
discr_place.hir_id,
);
self.walk_pat(discr_place, pat);
}
@ -634,6 +666,14 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
/// - When reporting the Place back to the Delegate, ensure that the UpvarId uses the enclosing
/// closure as the DefId.
fn walk_captures(&mut self, closure_expr: &hir::Expr<'_>) {
fn upvar_is_local_variable(
upvars: Option<&'tcx FxIndexMap<hir::HirId, hir::Upvar>>,
upvar_id: &hir::HirId,
body_owner_is_closure: bool,
) -> bool {
upvars.map(|upvars| !upvars.contains_key(upvar_id)).unwrap_or(body_owner_is_closure)
}
debug!("walk_captures({:?})", closure_expr);
let closure_def_id = self.tcx().hir().local_def_id(closure_expr.hir_id).to_def_id();
@ -645,16 +685,32 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
ty::Closure(..) | ty::Generator(..)
);
// If we have a nested closure, we want to include the fake reads present in the nested closure.
if let Some(fake_reads) = self.mc.typeck_results.closure_fake_reads.get(&closure_def_id) {
for (fake_read, cause) in fake_reads.iter() {
for (fake_read, cause, hir_id) in fake_reads.iter() {
match fake_read.base {
PlaceBase::Upvar(upvar_id) => {
if upvars.map_or(body_owner_is_closure, |upvars| {
!upvars.contains_key(&upvar_id.var_path.hir_id)
}) {
if upvar_is_local_variable(
upvars,
&upvar_id.var_path.hir_id,
body_owner_is_closure,
) {
// The nested closure might be fake reading the current (enclosing) closure's local variables.
// We check if the root variable is ever mentioned within the enclosing closure, if not
// then for the current body (if it's a closure) these do not require fake_read, we will ignore them.
// The only places we want to fake read before creating the parent closure are the ones that
// are not local to it/ defined by it.
//
// ```rust,ignore(cannot-test-this-because-pseduo-code)
// let v1 = (0, 1);
// let c = || { // fake reads: v1
// let v2 = (0, 1);
// let e = || { // fake reads: v1, v2
// let (_, t1) = v1;
// let (_, t2) = v2;
// }
// }
// ```
// This check is performed when visiting the body of the outermost closure (`c`) and ensures
// that we don't add a fake read of v2 in c.
continue;
}
}
@ -665,7 +721,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
);
}
};
self.delegate.fake_read(fake_read.clone(), *cause);
self.delegate.fake_read(fake_read.clone(), *cause, *hir_id);
}
}