Skip to content

Commit

Permalink
Rollup merge of rust-lang#136554 - compiler-errors:opt-alias-variance…
Browse files Browse the repository at this point in the history
…s, 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
  • Loading branch information
matthiaskrgr authored Feb 7, 2025
2 parents 8ca1a3d + d17a4a7 commit c4a2f88
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 50 deletions.
12 changes: 7 additions & 5 deletions compiler/rustc_borrowck/src/type_check/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ where
return;
}

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

Expand Down Expand Up @@ -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);
Expand Down
10 changes: 4 additions & 6 deletions compiler/rustc_infer/src/infer/outlives/for_liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_infer/src/infer/outlives/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
};

Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
23 changes: 23 additions & 0 deletions compiler/rustc_middle/src/ty/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_type_ir/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down
18 changes: 7 additions & 11 deletions compiler/rustc_type_ir/src/outlives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()));
}
}
Expand Down Expand Up @@ -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() };

Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_type_ir/src/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<...>`.
Expand Down
28 changes: 7 additions & 21 deletions compiler/rustc_type_ir/src/relate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/impl-trait/precise-capturing/rpitit-outlives-2.rs
Original file line number Diff line number Diff line change
@@ -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() {}
18 changes: 18 additions & 0 deletions tests/ui/impl-trait/precise-capturing/rpitit-outlives.rs
Original file line number Diff line number Diff line change
@@ -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() {}

0 comments on commit c4a2f88

Please sign in to comment.