Skip to content
Merged
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
1 change: 1 addition & 0 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ pub(crate) fn type_check<'tcx>(
typeck.infcx.destructure_solver_region_constraints_for_borrowck(
&mut converter,
typeck.known_type_outlives_obligations,
typeck.region_bound_pairs,
universal_region_relations.outlives.clone(),
);
}
Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2406,8 +2406,12 @@ 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());
// Deliberately unelaborated: the assumptions of a `forall` are exactly the ones
// written down in the test, no extra ones hidden behind the scenes.
let assumptions = ty::region_constraint::Assumptions::new_unelaborated(
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
48 changes: 43 additions & 5 deletions compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@
//! might later infer `?U` to something like `&'b u32`, which would
//! imply that `'b: 'a`.

use rustc_data_structures::transitive_relation::TransitiveRelation;
use rustc_data_structures::transitive_relation::{TransitiveRelation, TransitiveRelationBuilder};
use rustc_data_structures::undo_log::UndoLogs;
use rustc_middle::bug;
use rustc_middle::mir::ConstraintCategory;
use rustc_middle::ty::outlives::{Component, push_outlives_components};
use rustc_middle::ty::{
self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesClause, Region, RegionVid, Ty, TyCtxt,
TypeVisitableExt, eager_resolve_vars,
TypeVisitableExt, Upcast, eager_resolve_vars,
};
use rustc_span::Span;
use rustc_type_ir::region_constraint::{self, LeafRegionConstraint};
Expand Down Expand Up @@ -234,9 +234,22 @@ impl<'tcx> InferCtxt<'tcx> {
&self,
outlives_env: &OutlivesEnvironment<'tcx>,
) {
// `FreeRegionMap::relation` stores `'sub <= 'sup` edges while
// `Assumptions::region_outlives` expects `'longer: 'shorter` ones, so the
// edges have to be inverted here.
Comment thread
BoxyUwU marked this conversation as resolved.
let mut region_outlives = TransitiveRelationBuilder::default();
for (r1, r2) in outlives_env.free_region_map().relation.base_edges() {
region_outlives.add(r2, r1);
}
let assumptions = rustc_type_ir::region_constraint::Assumptions::new(
outlives_env.known_type_outlives().into_iter().cloned().collect(),
outlives_env.free_region_map().relation.clone(),
self,
assumed_type_outlives(
self.tcx,
outlives_env.known_type_outlives(),
outlives_env.region_bound_pairs(),
),
region_outlives.freeze(),
ty::UniverseIndex::ROOT,
);
self.destructure_solver_region_constraints(assumptions, self);
}
Expand All @@ -246,11 +259,14 @@ impl<'tcx> InferCtxt<'tcx> {
// this is always ConstraintConversion but lol
conversion: impl TypeOutlivesDelegate<'tcx>,
known_type_outlives: &[PolyTypeOutlivesClause<'tcx>],
region_bound_pairs: &RegionBoundPairs<'tcx>,
region_outlives: TransitiveRelation<RegionVid>,
) {
let assumptions = region_constraint::Assumptions::new(
known_type_outlives.into_iter().cloned().collect(),
self,
assumed_type_outlives(self.tcx, known_type_outlives, region_bound_pairs),
region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(),
ty::UniverseIndex::ROOT,
);
self.destructure_solver_region_constraints(assumptions, conversion);
}
Expand Down Expand Up @@ -376,6 +392,28 @@ impl<'tcx> InferCtxt<'tcx> {
}
}

/// The type outlives assumptions available in the root context, as clauses for
/// [`region_constraint::Assumptions::new`] to elaborate.
///
/// `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.
fn assumed_type_outlives<'tcx>(
tcx: TyCtxt<'tcx>,
known_type_outlives: &[PolyTypeOutlivesClause<'tcx>],
region_bound_pairs: &RegionBoundPairs<'tcx>,
) -> Vec<ty::Clause<'tcx>> {
known_type_outlives
.iter()
.copied()
.chain(region_bound_pairs.iter().map(|&ty::OutlivesClause(kind, r)| {
ty::Binder::dummy(ty::OutlivesClause(kind.to_ty(tcx), r))
}))
.map(|c| c.map_bound(ty::ClauseKind::TypeOutlives).upcast(tcx))
.collect()
}

/// The `TypeOutlives` struct has the job of "lowering" a `T: 'a`
/// obligation into a series of `'a: 'b` constraints and "verify"s, as
/// described on the module comment. The final constraints are emitted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#[cfg(feature = "nightly")]
use rustc_data_structures::transitive_relation::TransitiveRelationBuilder;
use rustc_type_ir::ClauseKind::*;
use rustc_type_ir::inherent::*;
use rustc_type_ir::outlives::{Component, push_outlives_components};
#[cfg(not(feature = "nightly"))]
Expand All @@ -12,8 +11,8 @@ use rustc_type_ir::region_constraint::{
propagate_ambiguity,
};
use rustc_type_ir::{
AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, OutlivesClause, Region, TypeVisitable,
TypeVisitableExt, TypeVisitor, UniverseIndex, max_universe,
AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, Region, TypeVisitable, TypeVisitableExt,
TypeVisitor, UniverseIndex,
};
use tracing::{debug, instrument};

Expand Down Expand Up @@ -82,9 +81,6 @@ where
t.visit_with(&mut reqs_builder);
let reqs = reqs_builder.out;

let mut region_outlives_builder = TransitiveRelationBuilder::default();
let mut type_outlives = vec![];

// If there are inference variables in type outlives then we may not be able
// to elaborate to the full set of implied bounds right now. To avoid incorrectly
// NoSolution'ing when lifting constraints to a lower universe due to no usable
Expand All @@ -102,25 +98,17 @@ where

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

clauses.filter(move |clause| max_universe(&**self.delegate, *clause) == u).for_each(
|clause| match clause.kind().skip_binder() {
RegionOutlives(OutlivesClause(r1, r2)) => {
assert!(clause.kind().no_bound_vars().is_some());
region_outlives_builder.add(r1, r2);
}
TypeOutlives(p) => {
type_outlives.push(clause.kind().map_bound(|_| p));
}
_ => (),
},
);

Some(Assumptions::new(type_outlives, region_outlives_builder.freeze()))
//
// `Assumptions::new` elaborates, restricts the clauses to `u` and picks out the
// outlives ones for us, so we just hand over everything the requirements gave us.
let clauses = reqs.into_iter().filter_map(|goal| goal.predicate.as_clause());

Some(Assumptions::new(
&**self.delegate,
clauses,
TransitiveRelationBuilder::default().freeze(),
u,
))
}

#[instrument(level = "debug", skip(self), ret)]
Expand Down
70 changes: 67 additions & 3 deletions compiler/rustc_type_ir/src/region_constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,19 @@ use crate::fold::TypeSuperFoldable;
use crate::inherent::*;
use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo};
use crate::{
AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, InferCtxtLike,
Interner, IsRigid, OutlivesClause, Region, RegionKind, TyKind, TypeFoldable, TypeFolder,
TypingMode, UniverseIndex, Variance, max_universe, set_aliases_to_non_rigid,
AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, ClauseKind, DebruijnIndex,
InferCtxtLike, Interner, IsRigid, OutlivesClause, Region, RegionKind, TyKind, TypeFoldable,
TypeFolder, TypingMode, UniverseIndex, Variance, elaborate, max_universe,
set_aliases_to_non_rigid,
};

#[derive_where(Clone, Debug; I: Interner)]
pub struct Assumptions<I: Interner> {
pub type_outlives: Vec<Binder<I, OutlivesClause<I, I::Ty>>>,
/// Known `'a: 'b` assumptions, stored as an edge from the outliving region to the
/// outlived one, i.e. an edge `('a, 'b)` means `'a: 'b`. Constructors expect a relation
/// with this direction, see [`regions_outlived_by`] and [`regions_outliving`] for how it
/// is consumed.
pub region_outlives: TransitiveRelation<Region<I>>,
pub inverse_region_outlives: TransitiveRelation<Region<I>>,
}
Expand All @@ -72,7 +77,66 @@ impl<I: Interner> Assumptions<I> {
}
}

/// Builds assumptions from `clauses`, elaborating them and keeping the outlives ones.
///
/// Callers hand us their clauses straight from the environment, so we have to elaborate
/// here to get at the implied outlives bounds:
/// - a `Ty: 'a` clause tells us that every region component of `Ty` outlives `'a`, e.g.
/// `&'b u8: 'a` implies `'b: 'a`. Without it we'd fail to prove `'b: 'a` when leaving
/// the binder these assumptions belong to.
/// - it also gives us the components as type outlives, e.g. `Vec<T>: 'a` implies `T: 'a`,
/// which we need for placeholder and alias outlives.
/// - trait clauses imply their supertraits, so `T: Bound<'a>` where `trait Bound<'c>: 'c`
/// gives us `T: 'a`. This is why we take clauses rather than just the outlives ones:
/// filtering down to outlives before elaborating would throw those away.
///
/// Only the clauses whose max universe is exactly `universe` are kept, which is what the
/// solver wants when computing the assumptions of a single binder. This happens after
/// elaboration on purpose, so a clause whose regions live in more than one universe still
/// contributes its implied bounds to each of them: `(&'b u8, &'c u8): 'a` gives us
/// `'c: 'a` in `'c`s universe even though the clause itself is in `'b`s.
///
/// Use [`Assumptions::new_unelaborated`] when the caller needs the assumptions to be
/// exactly the clauses it passed in.
pub fn new(
infcx: &impl InferCtxtLike<Interner = I>,
clauses: impl IntoIterator<Item = I::Clause>,
region_outlives: TransitiveRelation<Region<I>>,
universe: UniverseIndex,
) -> Self {
let mut type_outlives = vec![];
let mut region_outlives_builder = TransitiveRelationBuilder::default();
for (r1, r2) in region_outlives.base_edges() {
region_outlives_builder.add(r1, r2);
}

let clauses = elaborate::elaborate(infcx.cx(), clauses)
.filter(|clause| max_universe(infcx, *clause) == universe);
for clause in clauses {
match clause.kind().skip_binder() {
// The type outlives assumptions are kept around as they are required for
// proving placeholder and alias outlives.
ClauseKind::TypeOutlives(_) => {
type_outlives.push(clause.as_type_outlives_clause().unwrap());
}
ClauseKind::RegionOutlives(OutlivesClause(r1, r2)) => {
// `elaborate` drops the components which are bound inside of the type and
// bails on `for<'a> Ty: 'a`, so both regions here are free even though the
// clause itself may still be under a binder.
debug_assert!(!r1.is_bound() && !r2.is_bound());
region_outlives_builder.add(r1, r2);
}
// Anything else can't be used as an outlives assumption.
_ => (),
}
}

Self::new_unelaborated(type_outlives, region_outlives_builder.freeze())
}

/// Builds assumptions from exactly the given clauses, see [`Assumptions::new`] for when
/// the clauses should get elaborated instead.
pub fn new_unelaborated(
type_outlives: Vec<Binder<I, OutlivesClause<I, I::Ty>>>,
region_outlives: TransitiveRelation<Region<I>>,
) -> Self {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//@ check-pass
//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally

// A trait clause implies its supertraits, so a `&'x (): Bound<'x>` requirement is also evidence
// for `&'x (): 'static`, which elaborates to the region assumption `'x: 'static`.
// `Assumptions::new` therefore takes clauses and elaborates them itself; handing it only the
// outlives clauses would drop the trait clause before it could imply anything.
//
// The clause has to mention the binder's own `'x` to survive the `max_universe == u` filter,
// while the supertrait outlives is on `'static` so that the assumption can discharge `'x: 'a`.
//
// Keeping the requirement binder-local matters: the `for<'x> Wrap<'x>: 'a` bound is proven at the
// call site below, so failing to discharge `'x: 'a` is a `NoSolution` inside the solver rather
// than a constraint escaping to the root. Constraints reaching the root are still dropped, so a
// shape which lets `'x` escape (e.g. requiring `T: 'x` for an outer `T`) would pass either way.
// Removing the `&'x (): Bound<'x>` clause below makes this fail, as does dropping trait clauses
// before elaborating.

trait Bound<'c>: 'static {}

struct Wrap<'x>(&'x ())
where
&'x (): Bound<'x>;

fn foo<'a>(_a: &'a u32)
where
for<'x> Wrap<'x>: 'a,
{
}

fn main() {
foo(&10);
}
26 changes: 26 additions & 0 deletions tests/ui/assumptions_on_binders/type-outlives-assumptions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//@ check-pass
//@ compile-flags: -Zassumptions-on-binders

// Regression test for rust-lang/project-assumptions-on-binders#19, based on the `syn` failure.
// The receiver gives us an implied `I: 'b` bound and `'b: 'a` lets that satisfy the object
// lifetime. The implied type bound has to be included in the root assumptions for that to work.
trait IterTrait<'a, T: 'a>: Iterator<Item = &'a T> {
fn clone_box<'b>(&'b self) -> Box<dyn IterTrait<'a, T> + 'a>
where
'b: 'a;
}

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

fn main() {}
Loading