even more unify Projection/Opaque in outlives code

This commit is contained in:
Ali MJ Al-Nasrawy 2023-01-15 19:38:31 +03:00
parent 65d2f2a5f9
commit a7a842027c
12 changed files with 90 additions and 151 deletions

View File

@ -359,9 +359,9 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
.insert(ty::OutlivesPredicate(GenericKind::Param(param_b), r_a));
}
OutlivesBound::RegionSubAlias(r_a, kind, alias_b) => {
OutlivesBound::RegionSubAlias(r_a, alias_b) => {
self.region_bound_pairs
.insert(ty::OutlivesPredicate(GenericKind::Alias(kind, alias_b), r_a));
.insert(ty::OutlivesPredicate(GenericKind::Alias(alias_b), r_a));
}
}
}

View File

@ -80,7 +80,7 @@ pub(crate) fn insert_outlives_predicate<'tcx>(
.or_insert(span);
}
Component::Alias(kind, alias) => {
Component::Alias(alias_ty) => {
// This would either arise from something like:
//
// ```
@ -99,13 +99,13 @@ pub(crate) fn insert_outlives_predicate<'tcx>(
//
// Here we want to add an explicit `where <T as Iterator>::Item: 'a`
// or `Opaque<T>: 'a` depending on the alias kind.
let ty: Ty<'tcx> = tcx.mk_ty(ty::Alias(kind, alias));
let ty = alias_ty.to_ty(tcx);
required_predicates
.entry(ty::OutlivesPredicate(ty.into(), outlived_region))
.or_insert(span);
}
Component::EscapingProjection(_) => {
Component::EscapingAlias(_) => {
// As above, but the projection involves
// late-bound regions. Therefore, the WF
// requirement is not checked in type definition

View File

@ -2272,13 +2272,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
let labeled_user_string = match bound_kind {
GenericKind::Param(ref p) => format!("the parameter type `{}`", p),
GenericKind::Alias(ty::Projection, ref p) => format!("the associated type `{}`", p),
GenericKind::Alias(ty::Opaque, ref p) => {
format!(
"the opaque type `{}`",
self.tcx.def_path_str_with_substs(p.def_id, p.substs)
)
}
GenericKind::Alias(ref p) => match p.kind(self.tcx) {
ty::AliasKind::Projection => format!("the associated type `{}`", p),
ty::AliasKind::Opaque => format!("the opaque type `{}`", p),
},
};
if let Some(SubregionOrigin::CompareImplItemObligation {

View File

@ -22,7 +22,7 @@ pub enum Component<'tcx> {
// is not in a position to judge which is the best technique, so
// we just product the projection as a component and leave it to
// the consumer to decide (but see `EscapingProjection` below).
Alias(ty::AliasKind, ty::AliasTy<'tcx>),
Alias(ty::AliasTy<'tcx>),
// In the case where a projection has escaping regions -- meaning
// regions bound within the type itself -- we always use
@ -44,7 +44,7 @@ pub enum Component<'tcx> {
// projection, so that implied bounds code can avoid relying on
// them. This gives us room to improve the regionck reasoning in
// the future without breaking backwards compat.
EscapingProjection(Vec<Component<'tcx>>),
EscapingAlias(Vec<Component<'tcx>>),
}
/// Push onto `out` all the things that must outlive `'a` for the condition
@ -120,17 +120,6 @@ fn compute_components<'tcx>(
out.push(Component::Param(p));
}
// Ignore lifetimes found in opaque types. Opaque types can
// have lifetimes in their substs which their hidden type doesn't
// actually use. If we inferred that an opaque type is outlived by
// its parameter lifetimes, then we could prove that any lifetime
// outlives any other lifetime, which is unsound.
// See https://github.com/rust-lang/rust/issues/84305 for
// more details.
ty::Alias(ty::Opaque, data) => {
out.push(Component::Alias(ty::Opaque, data));
},
// For projections, we prefer to generate an obligation like
// `<P0 as Trait<P1...Pn>>::Foo: 'a`, because this gives the
// regionck more ways to prove that it holds. However,
@ -139,15 +128,15 @@ fn compute_components<'tcx>(
// trait-ref. Therefore, if we see any higher-ranked regions,
// we simply fallback to the most restrictive rule, which
// requires that `Pi: 'a` for all `i`.
ty::Alias(ty::Projection, data) => {
if !data.has_escaping_bound_vars() {
ty::Alias(_, alias_ty) => {
if !alias_ty.has_escaping_bound_vars() {
// best case: no escaping regions, so push the
// projection and skip the subtree (thus generating no
// constraints for Pi). This defers the choice between
// the rules OutlivesProjectionEnv,
// OutlivesProjectionTraitDef, and
// OutlivesProjectionComponents to regionck.
out.push(Component::Alias(ty::Projection, data));
out.push(Component::Alias(alias_ty));
} else {
// fallback case: hard code
// OutlivesProjectionComponents. Continue walking
@ -155,7 +144,7 @@ fn compute_components<'tcx>(
let mut subcomponents = smallvec![];
let mut subvisited = SsoHashSet::new();
compute_components_recursive(tcx, ty.into(), &mut subcomponents, &mut subvisited);
out.push(Component::EscapingProjection(subcomponents.into_iter().collect()));
out.push(Component::EscapingAlias(subcomponents.into_iter().collect()));
}
}

View File

@ -138,9 +138,9 @@ impl<'tcx> OutlivesEnvironmentBuilder<'tcx> {
self.region_bound_pairs
.insert(ty::OutlivesPredicate(GenericKind::Param(param_b), r_a));
}
OutlivesBound::RegionSubAlias(r_a, kind, projection_b) => {
OutlivesBound::RegionSubAlias(r_a, alias_b) => {
self.region_bound_pairs
.insert(ty::OutlivesPredicate(GenericKind::Alias(kind, projection_b), r_a));
.insert(ty::OutlivesPredicate(GenericKind::Alias(alias_b), r_a));
}
OutlivesBound::RegionSubRegion(r_a, r_b) => {
if let (ReEarlyBound(_) | ReFree(_), ReVar(vid_b)) = (r_a.kind(), r_b.kind()) {

View File

@ -67,7 +67,6 @@ use crate::infer::{
};
use crate::traits::{ObligationCause, ObligationCauseCode};
use rustc_data_structures::undo_log::UndoLogs;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::ConstraintCategory;
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, Region, SubstsRef, Ty, TyCtxt, TypeVisitable};
@ -266,10 +265,8 @@ where
Component::Param(param_ty) => {
self.param_ty_must_outlive(origin, region, *param_ty);
}
Component::Alias(kind, data) => {
self.alias_must_outlive(*kind, *data, origin, region)
}
Component::EscapingProjection(subcomponents) => {
Component::Alias(alias_ty) => self.alias_ty_must_outlive(origin, region, *alias_ty),
Component::EscapingAlias(subcomponents) => {
self.components_must_outlive(origin, &subcomponents, region, category);
}
Component::UnresolvedInferenceVariable(v) => {
@ -285,61 +282,26 @@ where
}
}
#[instrument(level = "debug", skip(self))]
fn param_ty_must_outlive(
&mut self,
origin: infer::SubregionOrigin<'tcx>,
region: ty::Region<'tcx>,
param_ty: ty::ParamTy,
) {
debug!(
"param_ty_must_outlive(region={:?}, param_ty={:?}, origin={:?})",
region, param_ty, origin
);
let generic = GenericKind::Param(param_ty);
let verify_bound = self.verify_bound.param_bound(param_ty);
self.delegate.push_verify(origin, generic, region, verify_bound);
self.delegate.push_verify(origin, GenericKind::Param(param_ty), region, verify_bound);
}
#[instrument(level = "debug", skip(self))]
fn alias_must_outlive(
&mut self,
kind: ty::AliasKind,
data: ty::AliasTy<'tcx>,
origin: infer::SubregionOrigin<'tcx>,
region: ty::Region<'tcx>,
) {
self.generic_must_outlive(
origin,
region,
GenericKind::Alias(kind, data),
data.def_id,
data.substs,
kind == ty::Opaque,
|ty| match *ty.kind() {
ty::Alias(filter_kind, ty::AliasTy { def_id, substs, .. })
if kind == filter_kind =>
{
(def_id, substs)
}
_ => bug!("expected only projection types from env, not {:?}", ty),
},
);
}
#[instrument(level = "debug", skip(self, filter))]
fn generic_must_outlive(
fn alias_ty_must_outlive(
&mut self,
origin: infer::SubregionOrigin<'tcx>,
region: ty::Region<'tcx>,
generic: GenericKind<'tcx>,
def_id: DefId,
substs: SubstsRef<'tcx>,
is_opaque: bool,
filter: impl Fn(Ty<'tcx>) -> (DefId, SubstsRef<'tcx>),
alias_ty: ty::AliasTy<'tcx>,
) {
// An optimization for a common case with opaque types.
if substs.is_empty() {
if alias_ty.substs.is_empty() {
return;
}
@ -361,14 +323,14 @@ where
// These are guaranteed to apply, no matter the inference
// results.
let trait_bounds: Vec<_> =
self.verify_bound.declared_region_bounds(def_id, substs).collect();
self.verify_bound.declared_bounds_from_definition(alias_ty).collect();
debug!(?trait_bounds);
// Compute the bounds we can derive from the environment. This
// is an "approximate" match -- in some cases, these bounds
// may not apply.
let mut approx_env_bounds = self.verify_bound.approx_declared_bounds_from_env(generic);
let mut approx_env_bounds = self.verify_bound.approx_declared_bounds_from_env(alias_ty);
debug!(?approx_env_bounds);
// Remove outlives bounds that we get from the environment but
@ -383,8 +345,8 @@ where
// If the declaration is `trait Trait<'b> { type Item: 'b; }`, then `projection_declared_bounds_from_trait`
// will be invoked with `['b => ^1]` and so we will get `^1` returned.
let bound = bound_outlives.skip_binder();
let (def_id, substs) = filter(bound.0);
self.verify_bound.declared_region_bounds(def_id, substs).all(|r| r != bound.1)
let ty::Alias(_, alias_ty) = bound.0.kind() else { bug!("expected AliasTy") };
self.verify_bound.declared_bounds_from_definition(*alias_ty).all(|r| r != bound.1)
});
// If declared bounds list is empty, the only applicable rule is
@ -401,12 +363,12 @@ where
// the problem is to add `T: 'r`, which isn't true. So, if there are no
// inference variables, we use a verify constraint instead of adding
// edges, which winds up enforcing the same condition.
let needs_infer = substs.needs_infer();
if approx_env_bounds.is_empty() && trait_bounds.is_empty() && (needs_infer || is_opaque) {
if approx_env_bounds.is_empty()
&& trait_bounds.is_empty()
&& (alias_ty.needs_infer() || alias_ty.kind(self.tcx) == ty::Opaque)
{
debug!("no declared bounds");
self.substs_must_outlive(substs, origin, region);
self.substs_must_outlive(alias_ty.substs, origin, region);
return;
}
@ -447,14 +409,9 @@ where
// projection outlive; in some cases, this may add insufficient
// edges into the inference graph, leading to inference failures
// even though a satisfactory solution exists.
let verify_bound = self.verify_bound.projection_opaque_bounds(
generic,
def_id,
substs,
&mut Default::default(),
);
debug!("projection_must_outlive: pushing {:?}", verify_bound);
self.delegate.push_verify(origin, generic, region, verify_bound);
let verify_bound = self.verify_bound.alias_bound(alias_ty, &mut Default::default());
debug!("alias_must_outlive: pushing {:?}", verify_bound);
self.delegate.push_verify(origin, GenericKind::Alias(alias_ty), region, verify_bound);
}
fn substs_must_outlive(

View File

@ -1,11 +1,10 @@
use crate::infer::outlives::components::{compute_components_recursive, Component};
use crate::infer::outlives::env::RegionBoundPairs;
use crate::infer::region_constraints::VerifyIfEq;
use crate::infer::{GenericKind, VerifyBound};
use crate::infer::VerifyBound;
use rustc_data_structures::sso::SsoHashSet;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::GenericArg;
use rustc_middle::ty::{self, OutlivesPredicate, SubstsRef, Ty, TyCtxt};
use rustc_middle::ty::{self, OutlivesPredicate, Ty, TyCtxt};
use smallvec::smallvec;
@ -94,29 +93,26 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
/// this list.
pub fn approx_declared_bounds_from_env(
&self,
generic: GenericKind<'tcx>,
alias_ty: ty::AliasTy<'tcx>,
) -> Vec<ty::Binder<'tcx, ty::OutlivesPredicate<Ty<'tcx>, ty::Region<'tcx>>>> {
let projection_ty = generic.to_ty(self.tcx);
let erased_projection_ty = self.tcx.erase_regions(projection_ty);
self.declared_generic_bounds_from_env_for_erased_ty(erased_projection_ty)
let erased_alias_ty = self.tcx.erase_regions(alias_ty.to_ty(self.tcx));
self.declared_generic_bounds_from_env_for_erased_ty(erased_alias_ty)
}
#[instrument(level = "debug", skip(self, visited))]
pub fn projection_opaque_bounds(
pub fn alias_bound(
&self,
generic: GenericKind<'tcx>,
def_id: DefId,
substs: SubstsRef<'tcx>,
alias_ty: ty::AliasTy<'tcx>,
visited: &mut SsoHashSet<GenericArg<'tcx>>,
) -> VerifyBound<'tcx> {
let generic_ty = generic.to_ty(self.tcx);
let alias_ty_as_ty = alias_ty.to_ty(self.tcx);
// Search the env for where clauses like `P: 'a`.
let projection_opaque_bounds = self
.approx_declared_bounds_from_env(generic)
let env_bounds = self
.approx_declared_bounds_from_env(alias_ty)
.into_iter()
.map(|binder| {
if let Some(ty::OutlivesPredicate(ty, r)) = binder.no_bound_vars() && ty == generic_ty {
if let Some(ty::OutlivesPredicate(ty, r)) = binder.no_bound_vars() && ty == alias_ty_as_ty {
// Micro-optimize if this is an exact match (this
// occurs often when there are no region variables
// involved).
@ -126,19 +122,19 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
VerifyBound::IfEq(verify_if_eq_b)
}
});
// Extend with bounds that we can find from the trait.
let trait_bounds =
self.declared_region_bounds(def_id, substs).map(|r| VerifyBound::OutlivedBy(r));
// Extend with bounds that we can find from the definition.
let definition_bounds =
self.declared_bounds_from_definition(alias_ty).map(|r| VerifyBound::OutlivedBy(r));
// see the extensive comment in projection_must_outlive
let recursive_bound = {
let mut components = smallvec![];
compute_components_recursive(self.tcx, generic_ty.into(), &mut components, visited);
compute_components_recursive(self.tcx, alias_ty_as_ty.into(), &mut components, visited);
self.bound_from_components(&components, visited)
};
VerifyBound::AnyBound(projection_opaque_bounds.chain(trait_bounds).collect())
.or(recursive_bound)
VerifyBound::AnyBound(env_bounds.chain(definition_bounds).collect()).or(recursive_bound)
}
fn bound_from_components(
@ -149,10 +145,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
let mut bounds = components
.iter()
.map(|component| self.bound_from_single_component(component, visited))
.filter(|bound| {
// Remove bounds that must hold, since they are not interesting.
!bound.must_hold()
});
// Remove bounds that must hold, since they are not interesting.
.filter(|bound| !bound.must_hold());
match (bounds.next(), bounds.next()) {
(Some(first), None) => first,
@ -170,13 +164,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
match *component {
Component::Region(lt) => VerifyBound::OutlivedBy(lt),
Component::Param(param_ty) => self.param_bound(param_ty),
Component::Alias(kind, data) => self.projection_opaque_bounds(
GenericKind::Alias(kind, data),
data.def_id,
data.substs,
visited,
),
Component::EscapingProjection(ref components) => {
Component::Alias(alias_ty) => self.alias_bound(alias_ty, visited),
Component::EscapingAlias(ref components) => {
self.bound_from_components(components, visited)
}
Component::UnresolvedInferenceVariable(v) => {
@ -292,16 +281,15 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
///
/// This is for simplicity, and because we are not really smart
/// enough to cope with such bounds anywhere.
pub fn declared_region_bounds(
pub fn declared_bounds_from_definition(
&self,
def_id: DefId,
substs: SubstsRef<'tcx>,
alias_ty: ty::AliasTy<'tcx>,
) -> impl Iterator<Item = ty::Region<'tcx>> {
let tcx = self.tcx;
let bounds = tcx.item_bounds(def_id);
let bounds = tcx.item_bounds(alias_ty.def_id);
trace!("{:#?}", bounds.0);
bounds
.subst_iter(tcx, substs)
.subst_iter(tcx, alias_ty.substs)
.filter_map(|p| p.to_opt_type_outlives())
.filter_map(|p| p.no_bound_vars())
.map(|OutlivesPredicate(_, r)| r)

View File

@ -167,7 +167,7 @@ pub struct Verify<'tcx> {
#[derive(Copy, Clone, PartialEq, Eq, Hash, TypeFoldable, TypeVisitable)]
pub enum GenericKind<'tcx> {
Param(ty::ParamTy),
Alias(ty::AliasKind, ty::AliasTy<'tcx>),
Alias(ty::AliasTy<'tcx>),
}
/// Describes the things that some `GenericKind` value `G` is known to
@ -746,10 +746,7 @@ impl<'tcx> fmt::Debug for GenericKind<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
GenericKind::Param(ref p) => write!(f, "{:?}", p),
GenericKind::Alias(ty::Projection, ref p) => write!(f, "{:?}", p),
GenericKind::Alias(ty::Opaque, ref p) => ty::tls::with(|tcx| {
write!(f, "{}", tcx.def_path_str_with_substs(p.def_id, tcx.lift(p.substs).unwrap()))
}),
GenericKind::Alias(ref p) => write!(f, "{:?}", p),
}
}
}
@ -758,10 +755,7 @@ impl<'tcx> fmt::Display for GenericKind<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
GenericKind::Param(ref p) => write!(f, "{}", p),
GenericKind::Alias(ty::Projection, ref p) => write!(f, "{}", p),
GenericKind::Alias(ty::Opaque, ref p) => ty::tls::with(|tcx| {
write!(f, "{}", tcx.def_path_str_with_substs(p.def_id, tcx.lift(p.substs).unwrap()))
}),
GenericKind::Alias(ref p) => write!(f, "{}", p),
}
}
}
@ -770,7 +764,7 @@ impl<'tcx> GenericKind<'tcx> {
pub fn to_ty(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
match *self {
GenericKind::Param(ref p) => p.to_ty(tcx),
GenericKind::Alias(kind, data) => tcx.mk_ty(ty::Alias(kind, data)),
GenericKind::Alias(ref p) => p.to_ty(tcx),
}
}
}

View File

@ -261,14 +261,15 @@ impl<'tcx> Elaborator<'tcx> {
Component::UnresolvedInferenceVariable(_) => None,
Component::Alias(kind, data) => {
let ty = tcx.mk_ty(ty::Alias(kind, data));
Component::Alias(alias_ty) => {
// We might end up here if we have `Foo<<Bar as Baz>::Assoc>: 'a`.
// With this, we can deduce that `<Bar as Baz>::Assoc: 'a`.
Some(ty::PredicateKind::Clause(ty::Clause::TypeOutlives(
ty::OutlivesPredicate(ty, r_min),
ty::OutlivesPredicate(alias_ty.to_ty(tcx), r_min),
)))
}
Component::EscapingProjection(_) => {
Component::EscapingAlias(_) => {
// We might be able to do more here, but we don't
// want to deal with escaping vars right now.
None

View File

@ -213,5 +213,5 @@ pub struct NormalizationResult<'tcx> {
pub enum OutlivesBound<'tcx> {
RegionSubRegion(ty::Region<'tcx>, ty::Region<'tcx>),
RegionSubParam(ty::Region<'tcx>, ty::ParamTy),
RegionSubAlias(ty::Region<'tcx>, ty::AliasKind, ty::AliasTy<'tcx>),
RegionSubAlias(ty::Region<'tcx>, ty::AliasTy<'tcx>),
}

View File

@ -1245,11 +1245,26 @@ pub struct AliasTy<'tcx> {
/// aka. `tcx.parent(def_id)`.
pub def_id: DefId,
/// This field exists to prevent the creation of `ProjectionTy` without using
/// This field exists to prevent the creation of `AliasTy` without using
/// [TyCtxt::mk_alias_ty].
pub(super) _use_mk_alias_ty_instead: (),
}
impl<'tcx> AliasTy<'tcx> {
pub fn kind(self, tcx: TyCtxt<'tcx>) -> ty::AliasKind {
match tcx.def_kind(self.def_id) {
DefKind::AssocTy | DefKind::ImplTraitPlaceholder => ty::Projection,
DefKind::OpaqueTy => ty::Opaque,
kind => bug!("unexpected DefKind in AliasTy: {kind:?}"),
}
}
pub fn to_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
tcx.mk_ty(ty::Alias(self.kind(tcx), self))
}
}
/// The following methods work only with associated type projections.
impl<'tcx> AliasTy<'tcx> {
pub fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId {
match tcx.def_kind(self.def_id) {
@ -1257,7 +1272,7 @@ impl<'tcx> AliasTy<'tcx> {
DefKind::ImplTraitPlaceholder => {
tcx.parent(tcx.impl_trait_in_trait_parent(self.def_id))
}
kind => bug!("unexpected DefKind in ProjectionTy: {kind:?}"),
kind => bug!("expected a projection AliasTy; found {kind:?}"),
}
}

View File

@ -154,10 +154,8 @@ fn implied_bounds_from_components<'tcx>(
match component {
Component::Region(r) => Some(OutlivesBound::RegionSubRegion(sub_region, r)),
Component::Param(p) => Some(OutlivesBound::RegionSubParam(sub_region, p)),
Component::Alias(kind, p) => {
Some(OutlivesBound::RegionSubAlias(sub_region, kind, p))
}
Component::EscapingProjection(_) =>
Component::Alias(p) => Some(OutlivesBound::RegionSubAlias(sub_region, p)),
Component::EscapingAlias(_) =>
// If the projection has escaping regions, don't
// try to infer any implied bounds even for its
// free components. This is conservative, because