Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider alias bounds when computing liveness in NLL (and opaque captures) #116040

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions compiler/rustc_borrowck/src/type_check/liveness/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_index::bit_set::HybridBitSet;
use rustc_index::interval::IntervalSet;
use rustc_infer::infer::canonical::QueryRegionConstraints;
use rustc_infer::infer::outlives::for_liveness::FreeRegionsVisitor;
use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
use rustc_middle::traits::query::DropckOutlivesResult;
use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt};
Expand Down Expand Up @@ -554,16 +555,17 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> {
"make_all_regions_live: live_at={}",
values::location_set_str(elements, live_at.iter()),
);

let tcx = typeck.tcx();
tcx.for_each_free_region(&value, |live_region| {
let live_region_vid =
typeck.borrowck_context.universal_regions.to_region_vid(live_region);
typeck
.borrowck_context
.constraints
.liveness_constraints
.add_elements(live_region_vid, live_at);
value.visit_with(&mut FreeRegionsVisitor {
tcx: typeck.tcx(),
param_env: typeck.param_env,
op: |r| {
let live_region_vid = typeck.borrowck_context.universal_regions.to_region_vid(r);
typeck
.borrowck_context
.constraints
.liveness_constraints
.add_elements(live_region_vid, live_at);
},
});
}

Expand Down
98 changes: 5 additions & 93 deletions compiler/rustc_infer/src/infer/opaque_types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use super::{DefineOpaqueTypes, InferResult};
use crate::errors::OpaqueHiddenTypeDiag;
use crate::infer::outlives::for_liveness::FreeRegionsVisitor;
use crate::infer::{InferCtxt, InferOk};
use crate::traits::{self, PredicateObligation};
use hir::def_id::{DefId, LocalDefId};
Expand All @@ -13,11 +14,10 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::fold::BottomUpFolder;
use rustc_middle::ty::GenericArgKind;
use rustc_middle::ty::{
self, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable,
TypeVisitable, TypeVisitableExt, TypeVisitor,
self, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeVisitable,
TypeVisitableExt,
};
use rustc_span::Span;
use std::ops::ControlFlow;

mod table;

Expand Down Expand Up @@ -380,8 +380,9 @@ impl<'tcx> InferCtxt<'tcx> {
.collect(),
);

concrete_ty.visit_with(&mut ConstrainOpaqueTypeRegionVisitor {
concrete_ty.visit_with(&mut FreeRegionsVisitor {
tcx: self.tcx,
param_env,
op: |r| self.member_constraint(opaque_type_key, span, concrete_ty, r, &choice_regions),
});
}
Expand Down Expand Up @@ -415,95 +416,6 @@ impl<'tcx> InferCtxt<'tcx> {
}
}

/// Visitor that requires that (almost) all regions in the type visited outlive
/// `least_region`. We cannot use `push_outlives_components` because regions in
/// closure signatures are not included in their outlives components. We need to
/// ensure all regions outlive the given bound so that we don't end up with,
/// say, `ReVar` appearing in a return type and causing ICEs when other
/// functions end up with region constraints involving regions from other
/// functions.
///
/// We also cannot use `for_each_free_region` because for closures it includes
/// the regions parameters from the enclosing item.
///
/// We ignore any type parameters because impl trait values are assumed to
/// capture all the in-scope type parameters.
pub struct ConstrainOpaqueTypeRegionVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> {
pub tcx: TyCtxt<'tcx>,
pub op: OP,
}

impl<'tcx, OP> TypeVisitor<TyCtxt<'tcx>> for ConstrainOpaqueTypeRegionVisitor<'tcx, OP>
where
OP: FnMut(ty::Region<'tcx>),
{
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
&mut self,
t: &ty::Binder<'tcx, T>,
) -> ControlFlow<Self::BreakTy> {
t.super_visit_with(self);
ControlFlow::Continue(())
}

fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
match *r {
// ignore bound regions, keep visiting
ty::ReLateBound(_, _) => ControlFlow::Continue(()),
_ => {
(self.op)(r);
ControlFlow::Continue(())
}
}
}

fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
// We're only interested in types involving regions
if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) {
return ControlFlow::Continue(());
}

match ty.kind() {
ty::Closure(_, ref args) => {
// Skip lifetime parameters of the enclosing item(s)

for upvar in args.as_closure().upvar_tys() {
upvar.visit_with(self);
}
args.as_closure().sig_as_fn_ptr_ty().visit_with(self);
}

ty::Generator(_, ref args, _) => {
// Skip lifetime parameters of the enclosing item(s)
// Also skip the witness type, because that has no free regions.

for upvar in args.as_generator().upvar_tys() {
upvar.visit_with(self);
}
args.as_generator().return_ty().visit_with(self);
args.as_generator().yield_ty().visit_with(self);
args.as_generator().resume_ty().visit_with(self);
}

ty::Alias(ty::Opaque, ty::AliasTy { def_id, ref args, .. }) => {
// Skip lifetime parameters that are not captures.
let variances = self.tcx.variances_of(*def_id);

for (v, s) in std::iter::zip(variances, args.iter()) {
if *v != ty::Variance::Bivariant {
s.visit_with(self);
}
}
}

_ => {
ty.super_visit_with(self);
}
}

ControlFlow::Continue(())
}
}

pub enum UseKind {
DefiningUse,
OpaqueUse,
Expand Down
163 changes: 163 additions & 0 deletions compiler/rustc_infer/src/infer/outlives/for_liveness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};

use std::ops::ControlFlow;

use crate::infer::outlives::test_type_match;
use crate::infer::region_constraints::VerifyIfEq;

/// Visits free regions in the type that are relevant for liveness computation.
/// These regions are passed to `OP`.
///
/// Specifically, we visit all of the regions of types recursively, except:
///
/// 1. If the type is an alias, we look at the outlives bounds in the param-env
/// and alias's item bounds. If there is a unique outlives bound, then visit
/// that instead. If there is not a unique but there is a `'static` outlives
/// bound, then don't visit anything. Otherwise, walk through the opaque's
/// regions structurally.
///
/// 2. If the type is a closure, only walk through the signature of the closure
/// and the upvars. Similarly, if the type is a generator, walk through the
/// three "signature" types (yield/resume/return) and its upvars. All generator
/// interior regions are bound regions, so ther is no need to walk through
/// that type.
///
/// # How does this differ from other region visitors?
///
/// We cannot use `push_outlives_components` because regions in closure
/// signatures are not included in their outlives components. We need to
/// ensure all regions outlive the given bound so that we don't end up with,
/// say, `ReVar` appearing in a return type and causing ICEs when other
/// functions end up with region constraints involving regions from other
/// functions.
///
/// We also cannot use `for_each_free_region` because for closures it includes
/// the regions parameters from the enclosing item, which we know are always
/// universal, so we don't particularly care about since they're not relevant
/// for opaque type captures or computing liveness.
pub struct FreeRegionsVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> {
pub tcx: TyCtxt<'tcx>,
pub param_env: ty::ParamEnv<'tcx>,
pub op: OP,
}

impl<'tcx, OP> TypeVisitor<TyCtxt<'tcx>> for FreeRegionsVisitor<'tcx, OP>
where
OP: FnMut(ty::Region<'tcx>),
{
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
&mut self,
t: &ty::Binder<'tcx, T>,
) -> ControlFlow<Self::BreakTy> {
t.super_visit_with(self);
ControlFlow::Continue(())
}

fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
match *r {
// ignore bound regions, keep visiting
ty::ReLateBound(_, _) => ControlFlow::Continue(()),
_ => {
(self.op)(r);
ControlFlow::Continue(())
}
}
}

fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
// We're only interested in types involving regions
if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) {
return ControlFlow::Continue(());
}

match ty.kind() {
ty::Closure(_, ref args) => {
// Skip lifetime parameters of the enclosing item(s)

for upvar in args.as_closure().upvar_tys() {
upvar.visit_with(self);
}
args.as_closure().sig_as_fn_ptr_ty().visit_with(self);
}

ty::Generator(_, ref args, _) => {
// Skip lifetime parameters of the enclosing item(s)
// Also skip the witness type, because that has no free regions.

for upvar in args.as_generator().upvar_tys() {
upvar.visit_with(self);
}
args.as_generator().return_ty().visit_with(self);
args.as_generator().yield_ty().visit_with(self);
args.as_generator().resume_ty().visit_with(self);
}

// We can prove that an alias is live two ways:
// 1. All the components are live.
//
// 2. There is a known outlives bound or where-clause, and that
// region is live.
//
// We search through the item bounds and where clauses for
// either `'static` or a unique outlives region, and if one is
// found, we just need to prove that that region is still live.
// If one is not found, then we continue to walk through the alias.
ty::Alias(kind, ty::AliasTy { def_id, args, .. }) => {
let tcx = self.tcx;
let param_env = self.param_env;
let outlives_bounds: Vec<_> = tcx
.item_bounds(def_id)
.iter_instantiated(tcx, args)
.chain(param_env.caller_bounds())
.filter_map(|clause| {
let outlives = clause.as_type_outlives_clause()?;
if let Some(outlives) = outlives.no_bound_vars()
&& outlives.0 == ty
{
Some(outlives.1)
} else {
test_type_match::extract_verify_if_eq(
tcx,
param_env,
&outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| {
VerifyIfEq { ty, bound }
}),
ty,
)
}
})
.collect();
// If we find `'static`, then we know the alias doesn't capture *any* regions.
// Otherwise, all of the outlives regions should be equal -- if they're not,
// we don't really know how to proceed, so we continue recursing through the
// alias.
if outlives_bounds.contains(&tcx.lifetimes.re_static) {
// no
} else if let Some(r) = outlives_bounds.first()
&& outlives_bounds[1..].iter().all(|other_r| other_r == r)
{
assert!(r.type_flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS));
r.visit_with(self)?;
} else {
// Skip lifetime parameters that are not captures.
let variances = match kind {
ty::Opaque => Some(self.tcx.variances_of(*def_id)),
_ => None,
};

for (idx, s) in args.iter().enumerate() {
if variances.map(|variances| variances[idx]) != Some(ty::Variance::Bivariant) {
s.visit_with(self)?;
}
}
}
}

_ => {
ty.super_visit_with(self)?;
}
}

ControlFlow::Continue(())
}
}
1 change: 1 addition & 0 deletions compiler/rustc_infer/src/infer/outlives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use rustc_middle::ty;

pub mod components;
pub mod env;
pub mod for_liveness;
pub mod obligations;
pub mod test_type_match;
pub mod verify;
Expand Down
20 changes: 20 additions & 0 deletions tests/ui/borrowck/alias-liveness/gat-static.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// check-pass

trait Foo {
type Assoc<'a>
where
Self: 'a;

fn assoc(&mut self) -> Self::Assoc<'_>;
}

fn test<T>(mut t: T)
where
T: Foo,
for<'a> T::Assoc<'a>: 'static,
{
let a = t.assoc();
let b = t.assoc();
}

fn main() {}
16 changes: 16 additions & 0 deletions tests/ui/borrowck/alias-liveness/higher-ranked.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// check-pass

trait Captures<'a> {}
impl<T> Captures<'_> for T {}

trait Outlives<'a>: 'a {}
impl<'a, T: 'a> Outlives<'a> for T {}

// Test that we treat `for<'a> Opaque: 'a` as `Opaque: 'static`
fn test<'o>(v: &'o Vec<i32>) -> impl Captures<'o> + for<'a> Outlives<'a> {}

fn statik() -> impl Sized {
test(&vec![])
}

fn main() {}
14 changes: 14 additions & 0 deletions tests/ui/borrowck/alias-liveness/opaque-capture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// check-pass

trait Captures<'a> {}
impl<T> Captures<'_> for T {}

fn captures_temp_early<'a>(x: &'a Vec<i32>) -> impl Sized + Captures<'a> + 'static {}
fn captures_temp_late<'a: 'a>(x: &'a Vec<i32>) -> impl Sized + Captures<'a> + 'static {}

fn test() {
let x = captures_temp_early(&vec![]);
let y = captures_temp_late(&vec![]);
}

fn main() {}
Loading
Loading