1
Fork 0

Correctly handle normalization in implied bounds

Special-case Bevy dependents to not error
This commit is contained in:
Ali MJ Al-Nasrawy 2023-12-10 20:13:21 -05:00 committed by Jack Huey
parent 6ae4cfbbb0
commit d96003dd2a
20 changed files with 375 additions and 170 deletions

View file

@ -191,10 +191,10 @@ pub fn all_fields_implement_trait<'tcx>(
// Check regions assuming the self type of the impl is WF
let outlives_env = OutlivesEnvironment::with_bounds(
param_env,
infcx.implied_bounds_tys(
infcx.implied_bounds_tys_compat(
param_env,
parent_cause.body_id,
FxIndexSet::from_iter([self_type]),
&FxIndexSet::from_iter([self_type]),
),
);
let errors = infcx.resolve_regions(&outlives_env);

View file

@ -9,123 +9,139 @@ use rustc_span::def_id::LocalDefId;
pub use rustc_middle::traits::query::OutlivesBound;
pub type BoundsCompat<'a, 'tcx: 'a> = impl Iterator<Item = OutlivesBound<'tcx>> + 'a;
pub type Bounds<'a, 'tcx: 'a> = impl Iterator<Item = OutlivesBound<'tcx>> + 'a;
pub trait InferCtxtExt<'a, 'tcx> {
fn implied_outlives_bounds(
&self,
fn implied_bounds_tys_compat(
&'a self,
param_env: ty::ParamEnv<'tcx>,
body_id: LocalDefId,
ty: Ty<'tcx>,
) -> Vec<OutlivesBound<'tcx>>;
tys: &'a FxIndexSet<Ty<'tcx>>,
) -> BoundsCompat<'a, 'tcx>;
fn implied_bounds_tys(
&'a self,
param_env: ty::ParamEnv<'tcx>,
body_id: LocalDefId,
tys: FxIndexSet<Ty<'tcx>>,
tys: &'a FxIndexSet<Ty<'tcx>>,
) -> Bounds<'a, 'tcx>;
}
impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> {
/// Implied bounds are region relationships that we deduce
/// automatically. The idea is that (e.g.) a caller must check that a
/// function's argument types are well-formed immediately before
/// calling that fn, and hence the *callee* can assume that its
/// argument types are well-formed. This may imply certain relationships
/// between generic parameters. For example:
/// ```
/// fn foo<T>(x: &T) {}
/// ```
/// can only be called with a `'a` and `T` such that `&'a T` is WF.
/// For `&'a T` to be WF, `T: 'a` must hold. So we can assume `T: 'a`.
///
/// # Parameters
///
/// - `param_env`, the where-clauses in scope
/// - `body_id`, the body-id to use when normalizing assoc types.
/// 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), ret)]
fn implied_outlives_bounds(
&self,
param_env: ty::ParamEnv<'tcx>,
body_id: LocalDefId,
ty: Ty<'tcx>,
) -> Vec<OutlivesBound<'tcx>> {
let ty = self.resolve_vars_if_possible(ty);
let ty = OpportunisticRegionResolver::new(self).fold_ty(ty);
/// Implied bounds are region relationships that we deduce
/// automatically. The idea is that (e.g.) a caller must check that a
/// function's argument types are well-formed immediately before
/// calling that fn, and hence the *callee* can assume that its
/// argument types are well-formed. This may imply certain relationships
/// between generic parameters. For example:
/// ```
/// fn foo<T>(x: &T) {}
/// ```
/// can only be called with a `'a` and `T` such that `&'a T` is WF.
/// For `&'a T` to be WF, `T: 'a` must hold. So we can assume `T: 'a`.
///
/// # Parameters
///
/// - `param_env`, the where-clauses in scope
/// - `body_id`, the body-id to use when normalizing assoc types.
/// 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(infcx, param_env, body_id), ret)]
fn implied_outlives_bounds<'a, 'tcx>(
infcx: &'a InferCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
body_id: LocalDefId,
ty: Ty<'tcx>,
compat: bool,
) -> Vec<OutlivesBound<'tcx>> {
let ty = infcx.resolve_vars_if_possible(ty);
let ty = OpportunisticRegionResolver::new(infcx).fold_ty(ty);
// We do not expect existential variables in implied bounds.
// We may however encounter unconstrained lifetime variables
// in very rare cases.
//
// See `ui/implied-bounds/implied-bounds-unconstrained-2.rs` for
// an example.
assert!(!ty.has_non_region_infer());
// We do not expect existential variables in implied bounds.
// We may however encounter unconstrained lifetime variables
// in very rare cases.
//
// See `ui/implied-bounds/implied-bounds-unconstrained-2.rs` for
// an example.
assert!(!ty.has_non_region_infer());
let mut canonical_var_values = OriginalQueryValues::default();
let canonical_ty = self.canonicalize_query(param_env.and(ty), &mut canonical_var_values);
let Ok(canonical_result) = self.tcx.implied_outlives_bounds(canonical_ty) else {
return vec![];
};
let mut canonical_var_values = OriginalQueryValues::default();
let canonical_ty = infcx.canonicalize_query(param_env.and(ty), &mut canonical_var_values);
let implied_bounds_result = if compat {
infcx.tcx.implied_outlives_bounds_compat(canonical_ty)
} else {
infcx.tcx.implied_outlives_bounds(canonical_ty)
};
let Ok(canonical_result) = implied_bounds_result else {
return vec![];
};
let mut constraints = QueryRegionConstraints::default();
let Ok(InferOk { value: mut bounds, obligations }) = self
.instantiate_nll_query_response_and_region_obligations(
&ObligationCause::dummy(),
let mut constraints = QueryRegionConstraints::default();
let Ok(InferOk { value: mut bounds, obligations }) = infcx
.instantiate_nll_query_response_and_region_obligations(
&ObligationCause::dummy(),
param_env,
&canonical_var_values,
canonical_result,
&mut constraints,
)
else {
return vec![];
};
assert_eq!(&obligations, &[]);
// Because of #109628, we may have unexpected placeholders. Ignore them!
// FIXME(#109628): panic in this case once the issue is fixed.
bounds.retain(|bound| !bound.has_placeholders());
if !constraints.is_empty() {
let span = infcx.tcx.def_span(body_id);
debug!(?constraints);
if !constraints.member_constraints.is_empty() {
span_bug!(span, "{:#?}", constraints.member_constraints);
}
// Instantiation may have produced new inference variables and constraints on those
// variables. Process these constraints.
let ocx = ObligationCtxt::new(infcx);
let cause = ObligationCause::misc(span, body_id);
for &constraint in &constraints.outlives {
ocx.register_obligation(infcx.query_outlives_constraint_to_obligation(
constraint,
cause.clone(),
param_env,
&canonical_var_values,
canonical_result,
&mut constraints,
)
else {
return vec![];
};
assert_eq!(&obligations, &[]);
));
}
// Because of #109628, we may have unexpected placeholders. Ignore them!
// FIXME(#109628): panic in this case once the issue is fixed.
bounds.retain(|bound| !bound.has_placeholders());
let errors = ocx.select_all_or_error();
if !errors.is_empty() {
infcx.dcx().span_delayed_bug(
span,
"implied_outlives_bounds failed to solve obligations from instantiation",
);
}
};
if !constraints.is_empty() {
let span = self.tcx.def_span(body_id);
bounds
}
debug!(?constraints);
if !constraints.member_constraints.is_empty() {
span_bug!(span, "{:#?}", constraints.member_constraints);
}
// Instantiation may have produced new inference variables and constraints on those
// variables. Process these constraints.
let ocx = ObligationCtxt::new(self);
let cause = ObligationCause::misc(span, body_id);
for &constraint in &constraints.outlives {
ocx.register_obligation(self.query_outlives_constraint_to_obligation(
constraint,
cause.clone(),
param_env,
));
}
let errors = ocx.select_all_or_error();
if !errors.is_empty() {
self.dcx().span_delayed_bug(
span,
"implied_outlives_bounds failed to solve obligations from instantiation",
);
}
};
bounds
impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> {
fn implied_bounds_tys_compat(
&'a self,
param_env: ParamEnv<'tcx>,
body_id: LocalDefId,
tys: &'a FxIndexSet<Ty<'tcx>>,
) -> BoundsCompat<'a, 'tcx> {
tys.iter().flat_map(move |ty| implied_outlives_bounds(self, param_env, body_id, *ty, true))
}
fn implied_bounds_tys(
&'a self,
param_env: ParamEnv<'tcx>,
body_id: LocalDefId,
tys: FxIndexSet<Ty<'tcx>>,
tys: &'a FxIndexSet<Ty<'tcx>>,
) -> Bounds<'a, 'tcx> {
tys.into_iter().flat_map(move |ty| self.implied_outlives_bounds(param_env, body_id, ty))
tys.iter().flat_map(move |ty| implied_outlives_bounds(self, param_env, body_id, *ty, false))
}
}

View file

@ -5,10 +5,11 @@ use crate::traits::ObligationCtxt;
use rustc_infer::infer::canonical::Canonical;
use rustc_infer::infer::outlives::components::{push_outlives_components, Component};
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
use rustc_infer::traits::query::OutlivesBound;
use rustc_middle::infer::canonical::CanonicalQueryResponse;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeFolder, TypeVisitableExt};
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::DUMMY_SP;
use smallvec::{smallvec, SmallVec};
@ -47,14 +48,14 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
param_env.and(ty)
});
tcx.implied_outlives_bounds(canonicalized)
tcx.implied_outlives_bounds_compat(canonicalized)
}
fn perform_locally_with_next_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution> {
compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty)
compute_implied_outlives_bounds_compat_inner(ocx, key.param_env, key.value.ty)
}
}
@ -62,6 +63,85 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
let normalize_op = |ty| {
let ty = ocx.normalize(&ObligationCause::dummy(), param_env, ty);
if !ocx.select_all_or_error().is_empty() {
return Err(NoSolution);
}
let ty = ocx.infcx.resolve_vars_if_possible(ty);
let ty = OpportunisticRegionResolver::new(&ocx.infcx).fold_ty(ty);
Ok(ty)
};
// Sometimes when we ask what it takes for T: WF, we get back that
// U: WF is required; in that case, we push U onto this stack and
// process it next. Because the resulting predicates aren't always
// guaranteed to be a subset of the original type, so we need to store the
// WF args we've computed in a set.
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
let mut wf_args = vec![ty.into(), normalize_op(ty)?.into()];
let mut outlives_bounds: Vec<OutlivesBound<'tcx>> = vec![];
while let Some(arg) = wf_args.pop() {
if !checked_wf_args.insert(arg) {
continue;
}
// From the full set of obligations, just filter down to the region relationships.
for obligation in
wf::unnormalized_obligations(ocx.infcx, param_env, arg).into_iter().flatten()
{
assert!(!obligation.has_escaping_bound_vars());
let Some(pred) = obligation.predicate.kind().no_bound_vars() else {
continue;
};
match pred {
// FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound
// if we ever support that
ty::PredicateKind::Clause(ty::ClauseKind::Trait(..))
| ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..))
| ty::PredicateKind::Subtype(..)
| ty::PredicateKind::Coerce(..)
| ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
| ty::PredicateKind::ObjectSafe(..)
| ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..))
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::Ambiguous
| ty::PredicateKind::NormalizesTo(..)
| ty::PredicateKind::AliasRelate(..) => {}
// We need to search through *all* WellFormed predicates
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => {
wf_args.push(arg);
}
// We need to register region relationships
ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(
ty::OutlivesPredicate(r_a, r_b),
)) => outlives_bounds.push(OutlivesBound::RegionSubRegion(r_b, r_a)),
ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(
ty_a,
r_b,
))) => {
let ty_a = normalize_op(ty_a)?;
let mut components = smallvec![];
push_outlives_components(ocx.infcx.tcx, ty_a, &mut components);
outlives_bounds.extend(implied_bounds_from_components(r_b, components))
}
}
}
}
Ok(outlives_bounds)
}
pub fn compute_implied_outlives_bounds_compat_inner<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
let tcx = ocx.infcx.tcx;