Auto merge of #82536 - sexxi-goose:handle-patterns-take-2, r=nikomatsakis
2229: Handle patterns within closures correctly when `capture_disjoint_fields` is enabled This PR fixes several issues related to handling patterns within closures when `capture_disjoint_fields` is enabled. 1. Matching is always considered a use of the place, even with `_` patterns 2. Compiler ICE when capturing fields in closures through `let` assignments To do so, we - Introduced new Fake Reads - Delayed use of `Place` in favor of `PlaceBuilder` - Ensured that `PlaceBuilder` can be resolved before attempting to extract `Place` in any of the pattern matching code Closes rust-lang/project-rfc-2229/issues/27 Closes rust-lang/project-rfc-2229/issues/24 r? `@nikomatsakis`
This commit is contained in:
commit
f5d8117c33
40 changed files with 1473 additions and 127 deletions
|
@ -41,6 +41,7 @@ use rustc_hir::def_id::LocalDefId;
|
|||
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
||||
use rustc_infer::infer::UpvarRegion;
|
||||
use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, Projection, ProjectionKind};
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty::fold::TypeFoldable;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults, UpvarSubsts};
|
||||
use rustc_session::lint;
|
||||
|
@ -145,6 +146,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
current_closure_kind: ty::ClosureKind::LATTICE_BOTTOM,
|
||||
current_origin: None,
|
||||
capture_information: Default::default(),
|
||||
fake_reads: Default::default(),
|
||||
};
|
||||
euv::ExprUseVisitor::new(
|
||||
&mut delegate,
|
||||
|
@ -246,6 +248,13 @@ 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, 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,
|
||||
// process any deferred resolutions.
|
||||
let deferred_call_resolutions = self.remove_deferred_call_resolutions(closure_def_id);
|
||||
|
@ -1148,6 +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, hir::HirId)>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
|
||||
|
@ -1409,6 +1419,12 @@ 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, diag_expr_id: hir::HirId) {
|
||||
if let PlaceBase::Upvar(_) = place.base {
|
||||
self.fake_reads.push((place, cause, diag_expr_id));
|
||||
}
|
||||
}
|
||||
|
||||
fn consume(
|
||||
&mut self,
|
||||
place_with_id: &PlaceWithHirId<'tcx>,
|
||||
|
|
|
@ -4,11 +4,15 @@
|
|||
|
||||
use crate::check::FnCtxt;
|
||||
|
||||
use rustc_data_structures::stable_map::FxHashMap;
|
||||
use rustc_errors::ErrorReported;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
||||
use rustc_infer::infer::error_reporting::TypeAnnotationNeeded::E0282;
|
||||
use rustc_infer::infer::InferCtxt;
|
||||
use rustc_middle::hir::place::Place as HirPlace;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCast};
|
||||
use rustc_middle::ty::fold::{TypeFoldable, TypeFolder};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
|
@ -56,6 +60,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
wbcx.visit_body(body);
|
||||
wbcx.visit_min_capture_map();
|
||||
wbcx.visit_fake_reads_map();
|
||||
wbcx.visit_upvar_capture_map();
|
||||
wbcx.visit_closures();
|
||||
wbcx.visit_liberated_fn_sigs();
|
||||
|
@ -363,6 +368,27 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
|
|||
self.typeck_results.closure_min_captures = min_captures_wb;
|
||||
}
|
||||
|
||||
fn visit_fake_reads_map(&mut self) {
|
||||
let mut resolved_closure_fake_reads: FxHashMap<
|
||||
DefId,
|
||||
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, 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, *hir_id));
|
||||
}
|
||||
resolved_closure_fake_reads.insert(*closure_def_id, resolved_fake_reads);
|
||||
}
|
||||
self.typeck_results.closure_fake_reads = resolved_closure_fake_reads;
|
||||
}
|
||||
|
||||
fn visit_upvar_capture_map(&mut self) {
|
||||
for (upvar_id, upvar_capture) in self.fcx.typeck_results.borrow().upvar_capture_map.iter() {
|
||||
let new_upvar_capture = match *upvar_capture {
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
pub use self::ConsumeMode::*;
|
||||
|
||||
// Export these here so that Clippy can use them.
|
||||
pub use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId, Projection};
|
||||
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;
|
||||
|
@ -14,6 +15,7 @@ use rustc_hir::PatKind;
|
|||
use rustc_index::vec::Idx;
|
||||
use rustc_infer::infer::InferCtxt;
|
||||
use rustc_middle::hir::place::ProjectionKind;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty::{self, adjustment, TyCtxt};
|
||||
use rustc_target::abi::VariantIdx;
|
||||
|
||||
|
@ -51,6 +53,9 @@ pub trait Delegate<'tcx> {
|
|||
// The path at `assignee_place` is being assigned to.
|
||||
// `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);
|
||||
|
||||
// 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)]
|
||||
|
@ -229,7 +234,61 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
|
|||
|
||||
hir::ExprKind::Match(ref discr, arms, _) => {
|
||||
let discr_place = return_if_err!(self.mc.cat_expr(&discr));
|
||||
self.borrow_expr(&discr, ty::ImmBorrow);
|
||||
|
||||
// Matching should not always be considered a use of the place, hence
|
||||
// discr does not necessarily need to be borrowed.
|
||||
// We only want to borrow discr if the pattern contain something other
|
||||
// than wildcards.
|
||||
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| {
|
||||
match &pat.kind {
|
||||
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 opt_sub_pat.is_none() {
|
||||
needs_to_be_read = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
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.
|
||||
for arm in arms {
|
||||
|
@ -518,6 +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,
|
||||
discr_place.hir_id,
|
||||
);
|
||||
self.walk_pat(discr_place, &arm.pat);
|
||||
|
||||
if let Some(hir::Guard::If(ref e)) = arm.guard {
|
||||
|
@ -530,6 +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,
|
||||
discr_place.hir_id,
|
||||
);
|
||||
self.walk_pat(discr_place, pat);
|
||||
}
|
||||
|
||||
|
@ -597,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();
|
||||
|
@ -608,6 +685,46 @@ 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, hir_id) in fake_reads.iter() {
|
||||
match fake_read.base {
|
||||
PlaceBase::Upvar(upvar_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.
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
bug!(
|
||||
"Do not know how to get HirId out of Rvalue and StaticItem {:?}",
|
||||
fake_read.base
|
||||
);
|
||||
}
|
||||
};
|
||||
self.delegate.fake_read(fake_read.clone(), *cause, *hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(min_captures) = self.mc.typeck_results.closure_min_captures.get(&closure_def_id)
|
||||
{
|
||||
for (var_hir_id, min_list) in min_captures.iter() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue