Skip to content
Open
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
7 changes: 5 additions & 2 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2395,8 +2395,11 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
for &(r1, r2) in &body.region_outlives {
builder.add(r1, r2);
}
let assumptions =
ty::region_constraint::Assumptions::new(body.type_outlives, builder.freeze());
let assumptions = ty::region_constraint::Assumptions::new(
self.tcx(),
body.type_outlives,
builder.freeze(),
);
self.infcx.insert_placeholder_assumptions(u, Some(assumptions));
self.check_test_binder_body(body.value);
let solver_region_constraint = self.infcx.get_solver_region_constraint();
Expand Down
17 changes: 16 additions & 1 deletion compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,22 @@ impl<'tcx> InferCtxt<'tcx> {
&self,
outlives_env: &OutlivesEnvironment<'tcx>,
) {
// `known_type_outlives` only contains the explicit `Ty: 'a` where clauses. The implied
// bounds, e.g. `T: 'a` from a `&'a T` argument, are only tracked in `region_bound_pairs`
// so we have to pull them in separately. Without them we'd fail to prove `T: 'a` for a
// `&'a T` argument whenever the only explicit bound on `T` mentions a different region.
let mut known_type_outlives = outlives_env.known_type_outlives().to_vec();
for &ty::OutlivesClause(kind, r) in outlives_env.region_bound_pairs() {
let ty = match kind {
GenericKind::Param(p) => Ty::new_param(self.tcx, p.index, p.name),
GenericKind::Placeholder(p) => Ty::new_placeholder(self.tcx, p),
GenericKind::Alias(alias) => alias.to_ty(self.tcx, ty::IsRigid::Yes),
};
known_type_outlives.push(ty::Binder::dummy(ty::OutlivesClause(ty, r)));
}
let assumptions = rustc_type_ir::region_constraint::Assumptions::new(
outlives_env.known_type_outlives().into_iter().cloned().collect(),
self.tcx,
known_type_outlives,
outlives_env.free_region_map().relation.clone(),
);
self.destructure_solver_region_constraints(assumptions, self);
Expand All @@ -239,6 +253,7 @@ impl<'tcx> InferCtxt<'tcx> {
region_outlives: TransitiveRelation<RegionVid>,
) {
let assumptions = rustc_type_ir::region_constraint::Assumptions::new(
self.tcx,
known_type_outlives.into_iter().cloned().collect(),
region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ where

// FIXME(-Zassumptions-on-binders): we need to normalize here/somewhere
// as we assume the type outlives assumptions only have rigid types :>
let cx = self.cx();
let clauses = rustc_type_ir::elaborate::elaborate(
self.cx(),
cx,
reqs.into_iter().filter_map(|goal| goal.predicate.as_clause()),
);

Expand All @@ -120,7 +121,7 @@ where
},
);

Some(Assumptions::new(type_outlives, region_outlives_builder.freeze()))
Some(Assumptions::new(cx, type_outlives, region_outlives_builder.freeze()))
}

#[instrument(level = "debug", skip(self), ret)]
Expand Down
61 changes: 61 additions & 0 deletions compiler/rustc_type_ir/src/region_constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ impl<T> Default for TransitiveRelationBuilder<T> {
use crate::data_structures::IndexMap;
use crate::fold::TypeSuperFoldable;
use crate::inherent::*;
use crate::outlives::{Component, push_outlives_components};
use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo};
use crate::{
AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, FallibleTypeFolder,
Expand All @@ -74,9 +75,50 @@ impl<I: Interner> Assumptions<I> {
}

pub fn new(
cx: I,
type_outlives: Vec<Binder<I, OutlivesClause<I, I::Ty>>>,
region_outlives: TransitiveRelation<Region<I>>,
) -> Self {
// A `Ty: 'a` assumption also tells us that every region component of `Ty` outlives `'a`,
// e.g. `&'b u8: 'a` implies `'b: 'a`. Callers do not necessarily hand us an elaborated set
// of assumptions so we destructure them here, otherwise we'd fail to prove `'b: 'a` when
// leaving the binder these assumptions belong to.
//
// The type outlives assumptions are still kept around as they are required for proving
// placeholder and alias outlives.
//
// This mirrors `elaborate`, in particular in how it deals with binders: they're simply
// skipped, so `for<'c> Foo<'a, 'c>: 'b` still gives us `'a: 'b`.
let mut implied_region_outlives = vec![];
for clause in &type_outlives {
let OutlivesClause(ty, r) = clause.clone().skip_binder();
// Ignore `for<'a> Ty: 'a`. We could treat this as evidence for `Ty: 'static` but
// `elaborate` conservatively doesn't, so neither do we.
if r.is_bound() {
continue;
}
let mut components = Default::default();
push_outlives_components(cx, ty, &mut components);
implied_region_outlives.extend(components.into_iter().filter_map(|c| match c {
// Regions bound *inside* of `ty` don't relate to anything in scope here.
Component::Region(c_r) if !c_r.is_bound() => Some((c_r, r)),
_ => None,
}));
}

let region_outlives = if implied_region_outlives.is_empty() {
region_outlives
} else {
let mut builder = TransitiveRelationBuilder::default();
for (r1, r2) in region_outlives.base_edges() {
builder.add(r1, r2);
}
for (r1, r2) in implied_region_outlives {
builder.add(r1, r2);
}
builder.freeze()
};

Self {
inverse_region_outlives: {
let mut builder = TransitiveRelationBuilder::default();
Expand Down Expand Up @@ -586,6 +628,11 @@ pub fn evaluate_solver_constraint<I: Interner, S: Clone + std::fmt::Debug>(
) -> RegionConstraint<I, S> {
use RegionConstraint::*;
match constraint {
// Outlives is reflexive. Discharging this here and not just when leaving a universe
// matters as constraints are also destructured in the root, where reflexive candidates
// are the whole reason an OR is satisfiable. E.g. `!T: 'a` with a `!T: 'a` assumption
// ends up as `Or([.., RegionOutlives('a, 'a)])`.
RegionOutlives(r1, r2, _) if r1 == r2 => RegionConstraint::new_true(),
Ambiguity(_)
| RegionOutlives(..)
| AliasTyOutlivesViaEnv(..)
Expand Down Expand Up @@ -681,6 +728,12 @@ fn pull_region_outlives_constraints_out_of_universe<
constraint
}
RegionOutlives(region_1, region_2, ()) => {
// Outlives is reflexive, so this holds no matter which universes are involved
// and regardless of whether we know the assumptions for `u`.
if region_1 == region_2 {
return RegionConstraint::new_true();
}

let region_1_u = max_universe(infcx, region_1);
let region_2_u = max_universe(infcx, region_2);

Expand All @@ -693,6 +746,14 @@ fn pull_region_outlives_constraints_out_of_universe<
None => return RegionConstraint::Ambiguity(()),
};

// The constraint may already be entailed by the assumptions of the binder we are
// leaving, e.g. `for<'a, 'b> where 'b: 'a { 'b: 'a }`. There is nothing to lift into
// a smaller universe in that case, and looking for lower universe candidates would
// wrongly result in `Or([])` whenever the placeholders have no lower universe bounds.
if regions_outlived_by(region_1, assumptions).any(|r| r == region_2) {
return RegionConstraint::new_true();
}

let mut candidates = vec![];
for ub in
regions_outlived_by(region_1, assumptions).filter(|r| max_universe(infcx, *r) < u)
Expand Down
28 changes: 28 additions & 0 deletions tests/ui/assumptions_on_binders/reflexive-outlives-in-root.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//@ check-pass
//@ compile-flags: -Zassumptions-on-binders

// Regression test for rust-lang/project-assumptions-on-binders#19, minimized from `syn`.
//
// Proving `I: '_` for the `&'_ self` receiver destructures to an OR over every region which
// `I` is known to outlive. Two things are needed for that OR to be satisfiable:
//
// - the implied bound `I: '_` from `&'_ self` has to be part of the root assumptions, not just
// the explicit `I: 'a` where clause, as `'a: '_` does not hold
// - the resulting `RegionOutlives('_, '_)` candidate has to be discharged, which happens when
// evaluating the constraint rather than when leaving a universe

trait IterTrait<'a, T: 'a>: Iterator<Item = &'a T> {
fn clone_box(&self) -> Box<dyn IterTrait<'a, T> + 'a>;
}

impl<'a, T, I> IterTrait<'a, T> for I
where
T: 'a,
I: Iterator<Item = &'a T> + Clone + 'a,
{
fn clone_box(&self) -> Box<dyn IterTrait<'a, T> + 'a> {
Box::new(self.clone())
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//@ check-pass
//@ compile-flags: -Zassumptions-on-binders

#![feature(test_binder_constraints, non_lifetime_binders)]
#![expect(incomplete_features)]

// Regression test for rust-lang/project-assumptions-on-binders#19.
//
// When leaving a binder we lift its region constraints into a smaller universe. Constraints
// which already hold inside of the binder have no lower universe candidates to be lifted to,
// so they used to turn into `Or([])`, i.e. `false`. They have to be discharged instead.

// Outlives is reflexive.
core::test_binder_constraints! {
impl<> {
forall<'a> {
'a: 'a
} expect {
}
}
}

// Directly entailed by an assumption of the binder we're leaving.
core::test_binder_constraints! {
impl<> {
forall<'a, 'b> where 'b: 'a {
'b: 'a
} expect {
}
}
}

// Transitively entailed by the assumptions of the binder we're leaving.
core::test_binder_constraints! {
impl<> {
forall<'a, 'b, 'c> where 'c: 'b, 'b: 'a {
'c: 'a
} expect {
}
}
}

// `&'b u8: 'a` implies `'b: 'a`, so type outlives assumptions have to be destructured into
// region outlives assumptions.
core::test_binder_constraints! {
impl<> {
forall<'a, 'b> where &'b u8: 'a {
'b: 'a
} expect {
}
}
}

// Binders in a type outlives assumption are skipped rather than bailed on, so
// `for<'c> fn(&'c (), &'b u8): 'a` still gives us `'b: 'a`.
core::test_binder_constraints! {
impl<> {
forall<'a, 'b> where for<'c> fn(&'c (), &'b u8): 'a {
'b: 'a
} expect {
}
}
}

// Discharging entailed constraints must not swallow the ones which still have to be lifted
// into the outer universe. Here `'a: 'a` and `'b: 'a` are discharged inside the binder while
// `'c: 'a` is lifted, as `'c` outlives every lower universe region that `'a` outlives.
//
// FIXME(-Zassumptions-on-binders): this should be `impl<'b, 'c: 'b>`, not
// `impl<'b, 'c: 'b + 'static>`, but OR isn't actually implemented yet
core::test_binder_constraints! {
impl<'b, 'c: 'b + 'static> {
forall<'a> where 'b: 'a {
'a: 'a,
'b: 'a,
'c: 'a,
} expect {
or {
'c: 'b,
'c: 'static,
}
}
}
}

fn main() {}
Loading