Skip to content

Commit

Permalink
improve infer var handling for implied bounds
Browse files Browse the repository at this point in the history
  • Loading branch information
lcnr committed Sep 19, 2022
1 parent efa717b commit 71f8fd5
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 34 deletions.
2 changes: 2 additions & 0 deletions compiler/rustc_infer/src/infer/outlives/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub struct OutlivesEnvironment<'tcx> {
}

/// Builder of OutlivesEnvironment.
#[derive(Debug)]
struct OutlivesEnvironmentBuilder<'tcx> {
param_env: ty::ParamEnv<'tcx>,
region_relation: TransitiveRelationBuilder<Region<'tcx>>,
Expand Down Expand Up @@ -109,6 +110,7 @@ impl<'tcx> OutlivesEnvironment<'tcx> {

impl<'a, 'tcx> OutlivesEnvironmentBuilder<'tcx> {
#[inline]
#[instrument(level = "debug")]
fn build(self) -> OutlivesEnvironment<'tcx> {
OutlivesEnvironment {
param_env: self.param_env,
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_trait_selection/src/traits/outlives_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl<'a, 'cx, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'cx, 'tcx> {
/// Note that this may cause outlives obligations to be injected
/// into the inference context with this body-id.
/// - `ty`, the type that we are supposed to assume is WF.
#[instrument(level = "debug", skip(self, param_env, body_id))]
#[instrument(level = "debug", skip(self, param_env, body_id), ret)]
fn implied_outlives_bounds(
&self,
param_env: ty::ParamEnv<'tcx>,
Expand All @@ -71,6 +71,7 @@ impl<'a, 'cx, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'cx, 'tcx> {
let TypeOpOutput { output, constraints, .. } = result;

if let Some(constraints) = constraints {
debug!(?constraints);
// Instantiation may have produced new inference variables and constraints on those
// variables. Process these constraints.
let mut fulfill_cx = <dyn TraitEngine<'tcx>>::new(self.tcx);
Expand Down
69 changes: 36 additions & 33 deletions compiler/rustc_traits/src/implied_outlives_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ fn compute_implied_outlives_bounds<'tcx>(
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
let mut wf_args = vec![ty.into()];

let mut implied_bounds = vec![];
let mut outlives_bounds: Vec<ty::OutlivesPredicate<ty::GenericArg<'tcx>, ty::Region<'tcx>>> =
vec![];

let mut fulfill_cx = <dyn TraitEngine<'tcx>>::new(tcx);

Expand All @@ -65,41 +66,28 @@ fn compute_implied_outlives_bounds<'tcx>(
// than the ultimate set. (Note: normally there won't be
// unresolved inference variables here anyway, but there might be
// during typeck under some circumstances.)
//
// FIXME(@lcnr): It's not really "always fine", having fewer implied
// bounds can be backward incompatible, e.g. #101951 was caused by
// us not dealing with inference vars in `TypeOutlives` predicates.
let obligations = wf::obligations(infcx, param_env, hir::CRATE_HIR_ID, 0, arg, DUMMY_SP)
.unwrap_or_default();

// N.B., all of these predicates *ought* to be easily proven
// true. In fact, their correctness is (mostly) implied by
// other parts of the program. However, in #42552, we had
// an annoying scenario where:
//
// - Some `T::Foo` gets normalized, resulting in a
// variable `_1` and a `T: Trait<Foo=_1>` constraint
// (not sure why it couldn't immediately get
// solved). This result of `_1` got cached.
// - These obligations were dropped on the floor here,
// rather than being registered.
// - Then later we would get a request to normalize
// `T::Foo` which would result in `_1` being used from
// the cache, but hence without the `T: Trait<Foo=_1>`
// constraint. As a result, `_1` never gets resolved,
// and we get an ICE (in dropck).
//
// Therefore, we register any predicates involving
// inference variables. We restrict ourselves to those
// involving inference variables both for efficiency and
// to avoids duplicate errors that otherwise show up.
// While these predicates should all be implied by other parts of
// the program, they are still relevant as they may constrain
// inference variables, which is necessary to add the correct
// implied bounds in some cases, mostly when dealing with projections.
fulfill_cx.register_predicate_obligations(
infcx,
obligations.iter().filter(|o| o.predicate.has_infer_types_or_consts()).cloned(),
);

// From the full set of obligations, just filter down to the
// region relationships.
implied_bounds.extend(obligations.into_iter().flat_map(|obligation| {
outlives_bounds.extend(obligations.into_iter().filter_map(|obligation| {
assert!(!obligation.has_escaping_bound_vars());
match obligation.predicate.kind().no_bound_vars() {
None => vec![],
None => None,
Some(pred) => match pred {
ty::PredicateKind::Trait(..)
| ty::PredicateKind::Subtype(..)
Expand All @@ -109,21 +97,18 @@ fn compute_implied_outlives_bounds<'tcx>(
| ty::PredicateKind::ObjectSafe(..)
| ty::PredicateKind::ConstEvaluatable(..)
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::TypeWellFormedFromEnv(..) => vec![],
| ty::PredicateKind::TypeWellFormedFromEnv(..) => None,
ty::PredicateKind::WellFormed(arg) => {
wf_args.push(arg);
vec![]
None
}

ty::PredicateKind::RegionOutlives(ty::OutlivesPredicate(r_a, r_b)) => {
vec![OutlivesBound::RegionSubRegion(r_b, r_a)]
Some(ty::OutlivesPredicate(r_a.into(), r_b))
}

ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(ty_a, r_b)) => {
let ty_a = infcx.resolve_vars_if_possible(ty_a);
let mut components = smallvec![];
push_outlives_components(tcx, ty_a, &mut components);
implied_bounds_from_components(r_b, components)
Some(ty::OutlivesPredicate(ty_a.into(), r_b))
}
},
}
Expand All @@ -133,9 +118,27 @@ fn compute_implied_outlives_bounds<'tcx>(
// Ensure that those obligations that we had to solve
// get solved *here*.
match fulfill_cx.select_all_or_error(infcx).as_slice() {
[] => Ok(implied_bounds),
_ => Err(NoSolution),
[] => (),
_ => return Err(NoSolution),
}

// We lazily compute the outlives components as
// `select_all_or_error` constrains inference variables.
let implied_bounds = outlives_bounds
.into_iter()
.flat_map(|ty::OutlivesPredicate(a, r_b)| match a.unpack() {
ty::GenericArgKind::Lifetime(r_a) => vec![OutlivesBound::RegionSubRegion(r_b, r_a)],
ty::GenericArgKind::Type(ty_a) => {
let ty_a = infcx.resolve_vars_if_possible(ty_a);
let mut components = smallvec![];
push_outlives_components(tcx, ty_a, &mut components);
implied_bounds_from_components(r_b, components)
}
ty::GenericArgKind::Const(_) => unreachable!(),
})
.collect();

Ok(implied_bounds)
}

/// When we have an implied bound that `T: 'a`, we can further break
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_typeck/src/check/compare_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ pub(crate) fn compare_impl_method<'tcx>(
///
/// Finally we register each of these predicates as an obligation and check that
/// they hold.
#[instrument(level = "debug", skip(tcx, impl_m_span, impl_trait_ref))]
fn compare_predicate_entailment<'tcx>(
tcx: TyCtxt<'tcx>,
impl_m: &AssocItem,
Expand Down

0 comments on commit 71f8fd5

Please sign in to comment.