1
Fork 0

Rollup merge of #136554 - compiler-errors:opt-alias-variances, r=lcnr

Add `opt_alias_variances` and use it in outlives code

...so to fix some subtle outlives bugs with precise capturing in traits, and eventually make it easier to compute variances for "forced unconstrained" trait lifetimes.

r? lcnr
This commit is contained in:
Matthias Krüger 2025-02-07 21:31:00 +01:00 committed by GitHub
commit 7ca0fd18f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 117 additions and 50 deletions

View file

@ -284,7 +284,7 @@ where
return;
}
match ty.kind() {
match *ty.kind() {
ty::Closure(_, args) => {
// Skip lifetime parameters of the enclosing item(s)
@ -316,10 +316,12 @@ where
args.as_coroutine().resume_ty().visit_with(self);
}
ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => {
// Skip lifetime parameters that are not captures.
let variances = self.tcx.variances_of(*def_id);
ty::Alias(kind, ty::AliasTy { def_id, args, .. })
if let Some(variances) = self.tcx.opt_alias_variances(kind, def_id) =>
{
// Skip lifetime parameters that are not captured, since they do
// not need member constraints registered for them; we'll erase
// them (and hopefully in the future replace them with placeholders).
for (v, s) in std::iter::zip(variances, args.iter()) {
if *v != ty::Bivariant {
s.visit_with(self);

View file

@ -48,7 +48,7 @@ where
return ty.super_visit_with(self);
}
match ty.kind() {
match *ty.kind() {
// We can prove that an alias is live two ways:
// 1. All the components are live.
//
@ -95,11 +95,9 @@ where
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,
};
// Skip lifetime parameters that are not captured, since they do
// not need to be live.
let variances = tcx.opt_alias_variances(kind, def_id);
for (idx, s) in args.iter().enumerate() {
if variances.map(|variances| variances[idx]) != Some(ty::Bivariant) {

View file

@ -392,13 +392,13 @@ 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 is_opaque = alias_ty.kind(self.tcx) == ty::Opaque;
let kind = alias_ty.kind(self.tcx);
if approx_env_bounds.is_empty()
&& trait_bounds.is_empty()
&& (alias_ty.has_infer_regions() || is_opaque)
&& (alias_ty.has_infer_regions() || kind == ty::Opaque)
{
debug!("no declared bounds");
let opt_variances = is_opaque.then(|| self.tcx.variances_of(alias_ty.def_id));
let opt_variances = self.tcx.opt_alias_variances(kind, alias_ty.def_id);
self.args_must_outlive(alias_ty.args, origin, region, opt_variances);
return;
}

View file

@ -102,12 +102,11 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
#[instrument(level = "debug", skip(self))]
pub(crate) fn alias_bound(&self, alias_ty: ty::AliasTy<'tcx>) -> VerifyBound<'tcx> {
let alias_ty_as_ty = alias_ty.to_ty(self.tcx);
// Search the env for where clauses like `P: 'a`.
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 == alias_ty_as_ty
&& let ty::Alias(_, alias_ty_from_bound) = *ty.kind()
&& alias_ty_from_bound == alias_ty
{
// Micro-optimize if this is an exact match (this
// occurs often when there are no region variables
@ -127,7 +126,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
// see the extensive comment in projection_must_outlive
let recursive_bound = {
let mut components = smallvec![];
compute_alias_components_recursive(self.tcx, alias_ty_as_ty, &mut components);
let kind = alias_ty.kind(self.tcx);
compute_alias_components_recursive(self.tcx, kind, alias_ty, &mut components);
self.bound_from_components(&components)
};

View file

@ -194,6 +194,14 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
self.variances_of(def_id)
}
fn opt_alias_variances(
self,
kind: impl Into<ty::AliasTermKind>,
def_id: DefId,
) -> Option<&'tcx [ty::Variance]> {
self.opt_alias_variances(kind, def_id)
}
fn type_of(self, def_id: DefId) -> ty::EarlyBinder<'tcx, Ty<'tcx>> {
self.type_of(def_id)
}

View file

@ -948,6 +948,29 @@ impl<'tcx> TyCtxt<'tcx> {
ty
}
// Computes the variances for an alias (opaque or RPITIT) that represent
// its (un)captured regions.
pub fn opt_alias_variances(
self,
kind: impl Into<ty::AliasTermKind>,
def_id: DefId,
) -> Option<&'tcx [ty::Variance]> {
match kind.into() {
ty::AliasTermKind::ProjectionTy => {
if self.is_impl_trait_in_trait(def_id) {
Some(self.variances_of(def_id))
} else {
None
}
}
ty::AliasTermKind::OpaqueTy => Some(self.variances_of(def_id)),
ty::AliasTermKind::InherentTy
| ty::AliasTermKind::WeakTy
| ty::AliasTermKind::UnevaluatedConst
| ty::AliasTermKind::ProjectionConst => None,
}
}
}
struct OpaqueTypeExpander<'tcx> {

View file

@ -141,6 +141,12 @@ pub trait Interner:
type VariancesOf: Copy + Debug + SliceLike<Item = ty::Variance>;
fn variances_of(self, def_id: Self::DefId) -> Self::VariancesOf;
fn opt_alias_variances(
self,
kind: impl Into<ty::AliasTermKind>,
def_id: Self::DefId,
) -> Option<Self::VariancesOf>;
fn type_of(self, def_id: Self::DefId) -> ty::EarlyBinder<Self, Self::Ty>;
type AdtDef: AdtDef<Self>;

View file

@ -148,7 +148,7 @@ impl<I: Interner> TypeVisitor<I> for OutlivesCollector<'_, I> {
// 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(_, alias_ty) => {
ty::Alias(kind, 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
@ -162,7 +162,7 @@ impl<I: Interner> TypeVisitor<I> for OutlivesCollector<'_, I> {
// OutlivesProjectionComponents. Continue walking
// through and constrain Pi.
let mut subcomponents = smallvec![];
compute_alias_components_recursive(self.cx, ty, &mut subcomponents);
compute_alias_components_recursive(self.cx, kind, alias_ty, &mut subcomponents);
self.out.push(Component::EscapingAlias(subcomponents.into_iter().collect()));
}
}
@ -217,21 +217,17 @@ impl<I: Interner> TypeVisitor<I> for OutlivesCollector<'_, I> {
}
}
/// Collect [Component]s for *all* the args of `parent`.
/// Collect [Component]s for *all* the args of `alias_ty`.
///
/// This should not be used to get the components of `parent` itself.
/// This should not be used to get the components of `alias_ty` itself.
/// Use [push_outlives_components] instead.
pub fn compute_alias_components_recursive<I: Interner>(
cx: I,
alias_ty: I::Ty,
kind: ty::AliasTyKind,
alias_ty: ty::AliasTy<I>,
out: &mut SmallVec<[Component<I>; 4]>,
) {
let ty::Alias(kind, alias_ty) = alias_ty.kind() else {
unreachable!("can only call `compute_alias_components_recursive` on an alias type")
};
let opt_variances =
if kind == ty::Opaque { Some(cx.variances_of(alias_ty.def_id)) } else { None };
let opt_variances = cx.opt_alias_variances(kind, alias_ty.def_id);
let mut visitor = OutlivesCollector { cx, out, visited: Default::default() };

View file

@ -469,6 +469,17 @@ impl AliasTermKind {
}
}
impl From<ty::AliasTyKind> for AliasTermKind {
fn from(value: ty::AliasTyKind) -> Self {
match value {
ty::Projection => AliasTermKind::ProjectionTy,
ty::Opaque => AliasTermKind::OpaqueTy,
ty::Weak => AliasTermKind::WeakTy,
ty::Inherent => AliasTermKind::InherentTy,
}
}
}
/// Represents the unprojected term of a projection goal.
///
/// * For a projection, this would be `<Ty as Trait<...>>::N<...>`.

View file

@ -236,28 +236,14 @@ impl<I: Interner> Relate<I> for ty::AliasTy<I> {
ExpectedFound::new(a, b)
}))
} else {
let args = match a.kind(relation.cx()) {
ty::Opaque => relate_args_with_variances(
relation,
a.def_id,
relation.cx().variances_of(a.def_id),
a.args,
b.args,
let cx = relation.cx();
let args = if let Some(variances) = cx.opt_alias_variances(a.kind(cx), a.def_id) {
relate_args_with_variances(
relation, a.def_id, variances, a.args, b.args,
false, // do not fetch `type_of(a_def_id)`, as it will cause a cycle
)?,
ty::Projection if relation.cx().is_impl_trait_in_trait(a.def_id) => {
relate_args_with_variances(
relation,
a.def_id,
relation.cx().variances_of(a.def_id),
a.args,
b.args,
false, // do not fetch `type_of(a_def_id)`, as it will cause a cycle
)?
}
ty::Projection | ty::Weak | ty::Inherent => {
relate_args_invariantly(relation, a.args, b.args)?
}
)?
} else {
relate_args_invariantly(relation, a.args, b.args)?
};
Ok(ty::AliasTy::new_from_args(relation.cx(), a.def_id, args))
}

View file

@ -0,0 +1,19 @@
//@ check-pass
// Ensure that we skip uncaptured args from RPITITs when comptuing outlives.
#![feature(precise_capturing_in_traits)]
struct Invariant<T>(*mut T);
trait Foo {
fn hello<'s: 's>(&'s self) -> Invariant<impl Sized + use<Self>>;
}
fn outlives_static(_: impl Sized + 'static) {}
fn hello<'s, T: Foo + 'static>(x: &'s T) {
outlives_static(x.hello());
}
fn main() {}

View file

@ -0,0 +1,18 @@
//@ check-pass
// Ensure that we skip uncaptured args from RPITITs when collecting the regions
// to enforce member constraints in opaque type inference.
#![feature(precise_capturing_in_traits)]
struct Invariant<T>(*mut T);
trait Foo {
fn hello<'s: 's>(&'s self) -> Invariant<impl Sized + use<Self>>;
}
fn hello<'s, T: Foo>(x: &'s T) -> Invariant<impl Sized> {
x.hello()
}
fn main() {}