Auto merge of #101680 - jackh726:implied-cleanup, r=lcnr

Fix implied outlives bounds logic for projections

The logic here is subtly wrong. I put a bit of an explanation in a767d7b5165cea8ee5cbe494a4a636c50ef67c9c.

TL;DR: we register outlives predicates to be proved, because wf code normalizes projections (from the unnormalized types) to type variables. This causes us to register those as constraints instead of implied. This was "fine", because we later added that implied bound in the normalized type, and delayed registering constraints. When I went to cleanup `free_region_relations` to *not* delay adding constraints, this bug was uncovered.

cc. `@aliemjay` because this caused your test failure in #99832 (I only realized as I was writing this)

r? `@nikomatsakis`
This commit is contained in:
bors 2023-02-10 03:21:39 +00:00
commit a697573463
7 changed files with 146 additions and 108 deletions

View File

@ -187,6 +187,7 @@ pub(crate) struct PlaceholderIndices {
} }
impl PlaceholderIndices { impl PlaceholderIndices {
/// Returns the `PlaceholderIndex` for the inserted `PlaceholderRegion`
pub(crate) fn insert(&mut self, placeholder: ty::PlaceholderRegion) -> PlaceholderIndex { pub(crate) fn insert(&mut self, placeholder: ty::PlaceholderRegion) -> PlaceholderIndex {
let (index, _) = self.indices.insert_full(placeholder); let (index, _) = self.indices.insert_full(placeholder);
index.into() index.into()

View File

@ -8,6 +8,7 @@ use rustc_infer::infer::InferCtxt;
use rustc_middle::mir::ConstraintCategory; use rustc_middle::mir::ConstraintCategory;
use rustc_middle::traits::query::OutlivesBound; use rustc_middle::traits::query::OutlivesBound;
use rustc_middle::ty::{self, RegionVid, Ty}; use rustc_middle::ty::{self, RegionVid, Ty};
use rustc_span::Span;
use rustc_trait_selection::traits::query::type_op::{self, TypeOp}; use rustc_trait_selection::traits::query::type_op::{self, TypeOp};
use std::rc::Rc; use std::rc::Rc;
use type_op::TypeOpOutput; use type_op::TypeOpOutput;
@ -217,8 +218,27 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
self.inverse_outlives.add(fr_b, fr_a); self.inverse_outlives.add(fr_b, fr_a);
} }
#[instrument(level = "debug", skip(self))]
pub(crate) fn create(mut self) -> CreateResult<'tcx> { pub(crate) fn create(mut self) -> CreateResult<'tcx> {
let span = self.infcx.tcx.def_span(self.universal_regions.defining_ty.def_id()); let span = self.infcx.tcx.def_span(self.universal_regions.defining_ty.def_id());
// Insert the facts we know from the predicates. Why? Why not.
let param_env = self.param_env;
self.add_outlives_bounds(outlives::explicit_outlives_bounds(param_env));
// - outlives is reflexive, so `'r: 'r` for every region `'r`
// - `'static: 'r` for every region `'r`
// - `'r: 'fn_body` for every (other) universally quantified
// region `'r`, all of which are provided by our caller
let fr_static = self.universal_regions.fr_static;
let fr_fn_body = self.universal_regions.fr_fn_body;
for fr in self.universal_regions.universal_regions() {
debug!("build: relating free region {:?} to itself and to 'static", fr);
self.relate_universal_regions(fr, fr);
self.relate_universal_regions(fr_static, fr);
self.relate_universal_regions(fr, fr_fn_body);
}
let unnormalized_input_output_tys = self let unnormalized_input_output_tys = self
.universal_regions .universal_regions
.unnormalized_input_tys .unnormalized_input_tys
@ -236,78 +256,58 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
// the `relations` is built. // the `relations` is built.
let mut normalized_inputs_and_output = let mut normalized_inputs_and_output =
Vec::with_capacity(self.universal_regions.unnormalized_input_tys.len() + 1); Vec::with_capacity(self.universal_regions.unnormalized_input_tys.len() + 1);
let constraint_sets: Vec<_> = unnormalized_input_output_tys let mut constraints = vec![];
.flat_map(|ty| { for ty in unnormalized_input_output_tys {
debug!("build: input_or_output={:?}", ty); debug!("build: input_or_output={:?}", ty);
// We add implied bounds from both the unnormalized and normalized ty. // We add implied bounds from both the unnormalized and normalized ty.
// See issue #87748 // See issue #87748
let constraints_implied1 = self.add_implied_bounds(ty); let constraints_unnorm = self.add_implied_bounds(ty);
let TypeOpOutput { output: norm_ty, constraints: constraints1, .. } = self if let Some(c) = constraints_unnorm {
.param_env constraints.push(c)
.and(type_op::normalize::Normalize::new(ty)) }
.fully_perform(self.infcx) let TypeOpOutput { output: norm_ty, constraints: constraints_normalize, .. } = self
.unwrap_or_else(|_| { .param_env
let reported = self .and(type_op::normalize::Normalize::new(ty))
.infcx .fully_perform(self.infcx)
.tcx .unwrap_or_else(|_| {
.sess self.infcx
.delay_span_bug(span, &format!("failed to normalize {:?}", ty)); .tcx
TypeOpOutput { .sess
output: self.infcx.tcx.ty_error_with_guaranteed(reported), .delay_span_bug(span, &format!("failed to normalize {:?}", ty));
constraints: None, TypeOpOutput {
error_info: None, output: self.infcx.tcx.ty_error(),
} constraints: None,
}); error_info: None,
// Note: we need this in examples like }
// ``` });
// trait Foo { if let Some(c) = constraints_normalize {
// type Bar; constraints.push(c)
// fn foo(&self) -> &Self::Bar; }
// }
// impl Foo for () {
// type Bar = ();
// fn foo(&self) -> &() {}
// }
// ```
// Both &Self::Bar and &() are WF
let constraints_implied2 =
if ty != norm_ty { self.add_implied_bounds(norm_ty) } else { None };
normalized_inputs_and_output.push(norm_ty);
constraints1.into_iter().chain(constraints_implied1).chain(constraints_implied2)
})
.collect();
// Insert the facts we know from the predicates. Why? Why not. // Note: we need this in examples like
let param_env = self.param_env; // ```
self.add_outlives_bounds(outlives::explicit_outlives_bounds(param_env)); // trait Foo {
// type Bar;
// fn foo(&self) -> &Self::Bar;
// }
// impl Foo for () {
// type Bar = ();
// fn foo(&self) ->&() {}
// }
// ```
// Both &Self::Bar and &() are WF
if ty != norm_ty {
let constraints_norm = self.add_implied_bounds(norm_ty);
if let Some(c) = constraints_norm {
constraints.push(c)
}
}
// Finally: normalized_inputs_and_output.push(norm_ty);
// - outlives is reflexive, so `'r: 'r` for every region `'r`
// - `'static: 'r` for every region `'r`
// - `'r: 'fn_body` for every (other) universally quantified
// region `'r`, all of which are provided by our caller
let fr_static = self.universal_regions.fr_static;
let fr_fn_body = self.universal_regions.fr_fn_body;
for fr in self.universal_regions.universal_regions() {
debug!("build: relating free region {:?} to itself and to 'static", fr);
self.relate_universal_regions(fr, fr);
self.relate_universal_regions(fr_static, fr);
self.relate_universal_regions(fr, fr_fn_body);
} }
for data in &constraint_sets { for c in constraints {
constraint_conversion::ConstraintConversion::new( self.push_region_constraints(c, span);
self.infcx,
&self.universal_regions,
&self.region_bound_pairs,
self.implicit_region_bound,
self.param_env,
Locations::All(span),
span,
ConstraintCategory::Internal,
&mut self.constraints,
)
.convert_all(data);
} }
CreateResult { CreateResult {
@ -321,6 +321,24 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
} }
} }
#[instrument(skip(self, data), level = "debug")]
fn push_region_constraints(&mut self, data: &QueryRegionConstraints<'tcx>, span: Span) {
debug!("constraints generated: {:#?}", data);
constraint_conversion::ConstraintConversion::new(
self.infcx,
&self.universal_regions,
&self.region_bound_pairs,
self.implicit_region_bound,
self.param_env,
Locations::All(span),
span,
ConstraintCategory::Internal,
&mut self.constraints,
)
.convert_all(data);
}
/// Update the type of a single local, which should represent /// Update the type of a single local, which should represent
/// either the return type of the MIR or one of its arguments. At /// either the return type of the MIR or one of its arguments. At
/// the same time, compute and add any implied bounds that come /// the same time, compute and add any implied bounds that come
@ -332,6 +350,7 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> {
.and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty }) .and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty })
.fully_perform(self.infcx) .fully_perform(self.infcx)
.unwrap_or_else(|_| bug!("failed to compute implied bounds {:?}", ty)); .unwrap_or_else(|_| bug!("failed to compute implied bounds {:?}", ty));
debug!(?bounds, ?constraints);
self.add_outlives_bounds(bounds); self.add_outlives_bounds(bounds);
constraints constraints
} }

View File

@ -910,6 +910,8 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> {
} }
impl<'tcx> MirTypeckRegionConstraints<'tcx> { impl<'tcx> MirTypeckRegionConstraints<'tcx> {
/// Creates a `Region` for a given `PlaceholderRegion`, or returns the
/// region that corresponds to a previously created one.
fn placeholder_region( fn placeholder_region(
&mut self, &mut self,
infcx: &InferCtxt<'tcx>, infcx: &InferCtxt<'tcx>,

View File

@ -207,6 +207,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
/// ///
/// In some cases, such as when `erased_ty` represents a `ty::Param`, however, /// In some cases, such as when `erased_ty` represents a `ty::Param`, however,
/// the result is precise. /// the result is precise.
#[instrument(level = "debug", skip(self))]
fn declared_generic_bounds_from_env_for_erased_ty( fn declared_generic_bounds_from_env_for_erased_ty(
&self, &self,
erased_ty: Ty<'tcx>, erased_ty: Ty<'tcx>,

View File

@ -70,47 +70,61 @@ fn compute_implied_outlives_bounds<'tcx>(
let obligations = wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, DUMMY_SP) let obligations = wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, DUMMY_SP)
.unwrap_or_default(); .unwrap_or_default();
// While these predicates should all be implied by other parts of for obligation in obligations {
// the program, they are still relevant as they may constrain debug!(?obligation);
// inference variables, which is necessary to add the correct
// implied bounds in some cases, mostly when dealing with projections.
ocx.register_obligations(
obligations.iter().filter(|o| o.predicate.has_non_region_infer()).cloned(),
);
// From the full set of obligations, just filter down to the
// region relationships.
outlives_bounds.extend(obligations.into_iter().filter_map(|obligation| {
assert!(!obligation.has_escaping_bound_vars()); assert!(!obligation.has_escaping_bound_vars());
match obligation.predicate.kind().no_bound_vars() {
None => None, // While these predicates should all be implied by other parts of
Some(pred) => match pred { // the program, they are still relevant as they may constrain
ty::PredicateKind::Clause(ty::Clause::Trait(..)) // inference variables, which is necessary to add the correct
| ty::PredicateKind::Subtype(..) // implied bounds in some cases, mostly when dealing with projections.
| ty::PredicateKind::Coerce(..) //
| ty::PredicateKind::Clause(ty::Clause::Projection(..)) // Another important point here: we only register `Projection`
| ty::PredicateKind::ClosureKind(..) // predicates, since otherwise we might register outlives
| ty::PredicateKind::ObjectSafe(..) // predicates containing inference variables, and we don't
| ty::PredicateKind::ConstEvaluatable(..) // learn anything new from those.
| ty::PredicateKind::ConstEquate(..) if obligation.predicate.has_non_region_infer() {
| ty::PredicateKind::Ambiguous match obligation.predicate.kind().skip_binder() {
| ty::PredicateKind::TypeWellFormedFromEnv(..) => None, ty::PredicateKind::Clause(ty::Clause::Projection(..)) => {
ty::PredicateKind::WellFormed(arg) => { ocx.register_obligation(obligation.clone());
wf_args.push(arg);
None
} }
_ => {}
ty::PredicateKind::Clause(ty::Clause::RegionOutlives( }
ty::OutlivesPredicate(r_a, r_b),
)) => Some(ty::OutlivesPredicate(r_a.into(), r_b)),
ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate(
ty_a,
r_b,
))) => Some(ty::OutlivesPredicate(ty_a.into(), r_b)),
},
} }
}));
let pred = match obligation.predicate.kind().no_bound_vars() {
None => continue,
Some(pred) => pred,
};
match pred {
ty::PredicateKind::Clause(ty::Clause::Trait(..))
| ty::PredicateKind::Subtype(..)
| ty::PredicateKind::Coerce(..)
| ty::PredicateKind::Clause(ty::Clause::Projection(..))
| ty::PredicateKind::ClosureKind(..)
| ty::PredicateKind::ObjectSafe(..)
| ty::PredicateKind::ConstEvaluatable(..)
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::Ambiguous
| ty::PredicateKind::TypeWellFormedFromEnv(..) => {}
// We need to search through *all* WellFormed predicates
ty::PredicateKind::WellFormed(arg) => {
wf_args.push(arg);
}
// We need to register region relationships
ty::PredicateKind::Clause(ty::Clause::RegionOutlives(ty::OutlivesPredicate(
r_a,
r_b,
))) => outlives_bounds.push(ty::OutlivesPredicate(r_a.into(), r_b)),
ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate(
ty_a,
r_b,
))) => outlives_bounds.push(ty::OutlivesPredicate(ty_a.into(), r_b)),
}
}
} }
// This call to `select_all_or_error` is necessary to constrain inference variables, which we // This call to `select_all_or_error` is necessary to constrain inference variables, which we

View File

@ -4,6 +4,7 @@
#![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::untranslatable_diagnostic)]
#![deny(rustc::diagnostic_outside_of_impl)] #![deny(rustc::diagnostic_outside_of_impl)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(drain_filter)]
#![recursion_limit = "256"] #![recursion_limit = "256"]
#[macro_use] #[macro_use]

View File

@ -1,6 +1,6 @@
// Regression test for #52057. There is an implied bound // Regression test for #52057. There is an implied bound
// that `I: 'a` where `'a` is the lifetime of `self` in `parse_first`; // that `I: 'x` where `'x` is the lifetime of the reference `&mut Self::Input`
// but to observe that, one must normalize first. // in `parse_first`; but to observe that, one must normalize first.
// //
// run-pass // run-pass