Skip to content

-Zhigher-ranked-assumptions: Consider WF of coroutine witness when proving outlives assumptions #143545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
33 changes: 25 additions & 8 deletions compiler/rustc_borrowck/src/type_check/constraint_conversion.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::LocalDefId;
use rustc_infer::infer::canonical::QueryRegionConstraints;
use rustc_infer::infer::outlives::env::RegionBoundPairs;
Expand All @@ -7,7 +8,7 @@ use rustc_infer::infer::{InferCtxt, SubregionOrigin};
use rustc_infer::traits::query::type_op::DeeplyNormalize;
use rustc_middle::bug;
use rustc_middle::ty::{
self, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, fold_regions,
self, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, elaborate, fold_regions,
};
use rustc_span::Span;
use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput};
Expand Down Expand Up @@ -70,10 +71,12 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {

#[instrument(skip(self), level = "debug")]
pub(super) fn convert_all(&mut self, query_constraints: &QueryRegionConstraints<'tcx>) {
let QueryRegionConstraints { outlives } = query_constraints;
let QueryRegionConstraints { outlives, assumptions } = query_constraints;
let assumptions =
elaborate::elaborate_outlives_assumptions(self.infcx.tcx, assumptions.iter().copied());

for &(predicate, constraint_category) in outlives {
self.convert(predicate, constraint_category);
self.convert(predicate, constraint_category, &assumptions);
}
}

Expand Down Expand Up @@ -112,15 +115,20 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {

self.category = outlives_requirement.category;
self.span = outlives_requirement.blame_span;
self.convert(ty::OutlivesPredicate(subject, outlived_region), self.category);
self.convert(
ty::OutlivesPredicate(subject, outlived_region),
self.category,
&Default::default(),
);
}
(self.category, self.span, self.from_closure) = backup;
}

fn convert(
&mut self,
predicate: ty::OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>,
predicate: ty::ArgOutlivesPredicate<'tcx>,
constraint_category: ConstraintCategory<'tcx>,
higher_ranked_assumptions: &FxHashSet<ty::ArgOutlivesPredicate<'tcx>>,
) {
let tcx = self.infcx.tcx;
debug!("generate: constraints at: {:#?}", self.locations);
Expand Down Expand Up @@ -150,7 +158,15 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
}

let mut next_outlives_predicates = vec![];
for (ty::OutlivesPredicate(k1, r2), constraint_category) in outlives_predicates {
for (pred, constraint_category) in outlives_predicates {
// Constraint is implied by a coroutine's well-formedness.
if self.infcx.tcx.sess.opts.unstable_opts.higher_ranked_assumptions
&& higher_ranked_assumptions.contains(&pred)
{
continue;
}

let ty::OutlivesPredicate(k1, r2) = pred;
match k1.kind() {
GenericArgKind::Lifetime(r1) => {
let r1_vid = self.to_region_vid(r1);
Expand Down Expand Up @@ -266,14 +282,15 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
&self,
ty: Ty<'tcx>,
next_outlives_predicates: &mut Vec<(
ty::OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>,
ty::ArgOutlivesPredicate<'tcx>,
ConstraintCategory<'tcx>,
)>,
) -> Ty<'tcx> {
match self.param_env.and(DeeplyNormalize { value: ty }).fully_perform(self.infcx, self.span)
{
Ok(TypeOpOutput { output: ty, constraints, .. }) => {
if let Some(QueryRegionConstraints { outlives }) = constraints {
// FIXME(higher_ranked_auto): What should we do with the assumptions here?
if let Some(QueryRegionConstraints { outlives, assumptions: _ }) = constraints {
next_outlives_predicates.extend(outlives.iter().copied());
}
ty
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ pub(crate) fn type_check<'tcx>(
pre_obligations.is_empty(),
"there should be no incoming region obligations = {pre_obligations:#?}",
);
let pre_assumptions = infcx.take_registered_region_assumptions();
assert!(
pre_assumptions.is_empty(),
"there should be no incoming region assumptions = {pre_assumptions:#?}",
);

debug!(?normalized_inputs_and_output);

Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_hir_analysis/src/outlives/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use smallvec::smallvec;

/// Tracks the `T: 'a` or `'a: 'a` predicates that we have inferred
/// must be added to the struct header.
pub(crate) type RequiredPredicates<'tcx> =
FxIndexMap<ty::OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>, Span>;
pub(crate) type RequiredPredicates<'tcx> = FxIndexMap<ty::ArgOutlivesPredicate<'tcx>, Span>;

/// Given a requirement `T: 'a` or `'b: 'a`, deduce the
/// outlives_component and add it to `required_predicates`
Expand Down
28 changes: 26 additions & 2 deletions compiler/rustc_infer/src/infer/canonical/query_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,15 @@ impl<'tcx> InferCtxt<'tcx> {
}

let region_obligations = self.take_registered_region_obligations();
let region_assumptions = self.take_registered_region_assumptions();
debug!(?region_obligations);
let region_constraints = self.with_region_constraints(|region_constraints| {
make_query_region_constraints(tcx, region_obligations, region_constraints)
make_query_region_constraints(
tcx,
region_obligations,
region_constraints,
region_assumptions,
)
});
debug!(?region_constraints);

Expand Down Expand Up @@ -169,6 +175,11 @@ impl<'tcx> InferCtxt<'tcx> {
self.register_outlives_constraint(predicate, cause);
}

for assumption in &query_response.value.region_constraints.assumptions {
let assumption = instantiate_value(self.tcx, &result_args, *assumption);
self.register_region_assumption(assumption);
}

let user_result: R =
query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone());

Expand Down Expand Up @@ -292,6 +303,18 @@ impl<'tcx> InferCtxt<'tcx> {
}),
);

// FIXME(higher_ranked_auto): Optimize this to instantiate all assumptions
// at once, rather than calling `instantiate_value` repeatedly which may
// create more universes.
output_query_region_constraints.assumptions.extend(
query_response
.value
.region_constraints
.assumptions
.iter()
.map(|&r_c| instantiate_value(self.tcx, &result_args, r_c)),
);

let user_result: R =
query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone());

Expand Down Expand Up @@ -567,6 +590,7 @@ pub fn make_query_region_constraints<'tcx>(
tcx: TyCtxt<'tcx>,
outlives_obligations: Vec<TypeOutlivesConstraint<'tcx>>,
region_constraints: &RegionConstraintData<'tcx>,
assumptions: Vec<ty::ArgOutlivesPredicate<'tcx>>,
) -> QueryRegionConstraints<'tcx> {
let RegionConstraintData { constraints, verifys } = region_constraints;

Expand Down Expand Up @@ -602,5 +626,5 @@ pub fn make_query_region_constraints<'tcx>(
}))
.collect();

QueryRegionConstraints { outlives }
QueryRegionConstraints { outlives, assumptions }
}
15 changes: 14 additions & 1 deletion compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ pub struct InferCtxtInner<'tcx> {
/// that all type inference variables have been bound and so forth.
region_obligations: Vec<TypeOutlivesConstraint<'tcx>>,

/// The outlives bounds that we assume must hold about placeholders that
/// come from instantiating the binder of coroutine-witnesses. These bounds
/// are deduced from the well-formedness of the witness's types, and are
/// necessary because of the way we anonymize the regions in a coroutine,
/// which may cause types to no longer be considered well-formed.
region_assumptions: Vec<ty::ArgOutlivesPredicate<'tcx>>,

/// Caches for opaque type inference.
opaque_type_storage: OpaqueTypeStorage<'tcx>,
}
Expand All @@ -164,7 +171,8 @@ impl<'tcx> InferCtxtInner<'tcx> {
int_unification_storage: Default::default(),
float_unification_storage: Default::default(),
region_constraint_storage: Some(Default::default()),
region_obligations: vec![],
region_obligations: Default::default(),
region_assumptions: Default::default(),
opaque_type_storage: Default::default(),
}
}
Expand All @@ -174,6 +182,11 @@ impl<'tcx> InferCtxtInner<'tcx> {
&self.region_obligations
}

#[inline]
pub fn region_assumptions(&self) -> &[ty::ArgOutlivesPredicate<'tcx>] {
&self.region_assumptions
}

#[inline]
pub fn projection_cache(&mut self) -> traits::ProjectionCache<'_, 'tcx> {
self.projection_cache.with_log(&mut self.undo_log)
Expand Down
11 changes: 10 additions & 1 deletion compiler/rustc_infer/src/infer/outlives/env.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rustc_data_structures::fx::FxIndexSet;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_data_structures::transitive_relation::TransitiveRelationBuilder;
use rustc_middle::{bug, ty};
use tracing::debug;
Expand Down Expand Up @@ -39,6 +39,9 @@ pub struct OutlivesEnvironment<'tcx> {
/// optimized in the future, though.
region_bound_pairs: RegionBoundPairs<'tcx>,
known_type_outlives: Vec<ty::PolyTypeOutlivesPredicate<'tcx>>,
/// Assumptions that come from the well-formedness of coroutines that we prove
/// auto trait bounds for during the type checking of this body.
higher_ranked_assumptions: FxHashSet<ty::ArgOutlivesPredicate<'tcx>>,
}

/// "Region-bound pairs" tracks outlives relations that are known to
Expand All @@ -52,6 +55,7 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
param_env: ty::ParamEnv<'tcx>,
known_type_outlives: Vec<ty::PolyTypeOutlivesPredicate<'tcx>>,
extra_bounds: impl IntoIterator<Item = OutlivesBound<'tcx>>,
higher_ranked_assumptions: FxHashSet<ty::ArgOutlivesPredicate<'tcx>>,
) -> Self {
let mut region_relation = TransitiveRelationBuilder::default();
let mut region_bound_pairs = RegionBoundPairs::default();
Expand Down Expand Up @@ -88,6 +92,7 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
known_type_outlives,
free_region_map: FreeRegionMap { relation: region_relation.freeze() },
region_bound_pairs,
higher_ranked_assumptions,
}
}

Expand All @@ -102,4 +107,8 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
pub fn known_type_outlives(&self) -> &[ty::PolyTypeOutlivesPredicate<'tcx>] {
&self.known_type_outlives
}

pub fn higher_ranked_assumptions(&self) -> &FxHashSet<ty::ArgOutlivesPredicate<'tcx>> {
&self.higher_ranked_assumptions
}
}
21 changes: 19 additions & 2 deletions compiler/rustc_infer/src/infer/outlives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use super::region_constraints::{RegionConstraintData, UndoLog};
use super::{InferCtxt, RegionResolutionError, SubregionOrigin};
use crate::infer::free_regions::RegionRelations;
use crate::infer::lexical_region_resolve;
use crate::infer::region_constraints::Constraint;

pub mod env;
pub mod for_liveness;
Expand Down Expand Up @@ -54,18 +55,29 @@ impl<'tcx> InferCtxt<'tcx> {
}
};

let storage = {
let mut storage = {
let mut inner = self.inner.borrow_mut();
let inner = &mut *inner;
assert!(
self.tainted_by_errors().is_some() || inner.region_obligations.is_empty(),
"region_obligations not empty: {:#?}",
inner.region_obligations
inner.region_obligations,
);
assert!(!UndoLogs::<UndoLog<'_>>::in_snapshot(&inner.undo_log));
inner.region_constraint_storage.take().expect("regions already resolved")
};

// Filter out any region-region outlives assumptions that are implied by
// coroutine well-formedness.
if self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions {
storage.data.constraints.retain(|(constraint, _)| match *constraint {
Constraint::RegSubReg(r1, r2) => !outlives_env
.higher_ranked_assumptions()
.contains(&ty::OutlivesPredicate(r2.into(), r1)),
_ => true,
});
}

let region_rels = &RegionRelations::new(self.tcx, outlives_env.free_region_map());

let (lexical_region_resolutions, errors) =
Expand Down Expand Up @@ -93,6 +105,11 @@ impl<'tcx> InferCtxt<'tcx> {
"region_obligations not empty: {:#?}",
self.inner.borrow().region_obligations
);
assert!(
self.inner.borrow().region_assumptions.is_empty(),
"region_assumptions not empty: {:#?}",
self.inner.borrow().region_assumptions
);

self.inner.borrow_mut().unwrap_region_constraints().take_and_reset_data()
}
Expand Down
20 changes: 19 additions & 1 deletion compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ use crate::traits::{ObligationCause, ObligationCauseCode};
impl<'tcx> InferCtxt<'tcx> {
pub fn register_outlives_constraint(
&self,
ty::OutlivesPredicate(arg, r2): ty::OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>,
ty::OutlivesPredicate(arg, r2): ty::ArgOutlivesPredicate<'tcx>,
cause: &ObligationCause<'tcx>,
) {
match arg.kind() {
Expand Down Expand Up @@ -170,6 +170,16 @@ impl<'tcx> InferCtxt<'tcx> {
std::mem::take(&mut self.inner.borrow_mut().region_obligations)
}

pub fn register_region_assumption(&self, assumption: ty::ArgOutlivesPredicate<'tcx>) {
let mut inner = self.inner.borrow_mut();
inner.undo_log.push(UndoLog::PushRegionAssumption);
inner.region_assumptions.push(assumption);
}

pub fn take_registered_region_assumptions(&self) -> Vec<ty::ArgOutlivesPredicate<'tcx>> {
std::mem::take(&mut self.inner.borrow_mut().region_assumptions)
}

/// Process the region obligations that must be proven (during
/// `regionck`) for the given `body_id`, given information about
/// the region bounds in scope and so forth.
Expand Down Expand Up @@ -220,6 +230,14 @@ impl<'tcx> InferCtxt<'tcx> {
let (sup_type, sub_region) =
(sup_type, sub_region).fold_with(&mut OpportunisticRegionResolver::new(self));

if self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions
&& outlives_env
.higher_ranked_assumptions()
.contains(&ty::OutlivesPredicate(sup_type.into(), sub_region))
{
continue;
}

debug!(?sup_type, ?sub_region, ?origin);

let outlives = &mut TypeOutlives::new(
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_infer/src/infer/snapshot/undo_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub(crate) enum UndoLog<'tcx> {
RegionUnificationTable(sv::UndoLog<ut::Delegate<RegionVidKey<'tcx>>>),
ProjectionCache(traits::UndoLog<'tcx>),
PushTypeOutlivesConstraint,
PushRegionAssumption,
}

macro_rules! impl_from {
Expand Down Expand Up @@ -77,6 +78,9 @@ impl<'tcx> Rollback<UndoLog<'tcx>> for InferCtxtInner<'tcx> {
let popped = self.region_obligations.pop();
assert_matches!(popped, Some(_), "pushed region constraint but could not pop it");
}
UndoLog::PushRegionAssumption => {
self.region_assumptions.pop();
}
}
}
}
Expand Down
14 changes: 9 additions & 5 deletions compiler/rustc_middle/src/infer/canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,18 @@ pub struct QueryResponse<'tcx, R> {
#[derive(HashStable, TypeFoldable, TypeVisitable)]
pub struct QueryRegionConstraints<'tcx> {
pub outlives: Vec<QueryOutlivesConstraint<'tcx>>,
pub assumptions: Vec<ty::ArgOutlivesPredicate<'tcx>>,
}

impl QueryRegionConstraints<'_> {
/// Represents an empty (trivially true) set of region
/// constraints.
/// Represents an empty (trivially true) set of region constraints.
///
/// FIXME(higher_ranked_auto): This could still just be true if there are only assumptions?
/// Because I don't expect for us to get cases where an assumption from one query would
/// discharge a requirement from another query, which is a potential problem if we did throw
/// away these assumptions because there were no constraints.
pub fn is_empty(&self) -> bool {
self.outlives.is_empty()
self.outlives.is_empty() && self.assumptions.is_empty()
}
}

Expand Down Expand Up @@ -130,8 +135,7 @@ impl<'tcx, R> QueryResponse<'tcx, R> {
}
}

pub type QueryOutlivesConstraint<'tcx> =
(ty::OutlivesPredicate<'tcx, GenericArg<'tcx>>, ConstraintCategory<'tcx>);
pub type QueryOutlivesConstraint<'tcx> = (ty::ArgOutlivesPredicate<'tcx>, ConstraintCategory<'tcx>);

#[derive(Default)]
pub struct CanonicalParamEnvCache<'tcx> {
Expand Down
Loading
Loading