convert query-type-op to create query-region-constraint directly

This commit is contained in:
Niko Matsakis 2018-06-19 18:32:43 -04:00
parent a583269af5
commit 82169b6134
8 changed files with 177 additions and 185 deletions

View File

@ -17,7 +17,6 @@
//!
//! [c]: https://rust-lang-nursery.github.io/rustc-guide/traits/canonicalization.html
use either::Either;
use infer::canonical::substitute::substitute_value;
use infer::canonical::{
Canonical, CanonicalVarKind, CanonicalVarValues, CanonicalizedQueryResult, Certainty,
@ -29,7 +28,6 @@ use rustc_data_structures::indexed_vec::Idx;
use rustc_data_structures::indexed_vec::IndexVec;
use rustc_data_structures::sync::Lrc;
use std::fmt::Debug;
use std::iter::once;
use syntax::ast;
use traits::query::NoSolution;
use traits::{FulfillmentContext, TraitEngine};
@ -191,9 +189,11 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
pub fn instantiate_nll_query_result_and_region_obligations<R>(
&self,
cause: &ObligationCause<'tcx>,
param_env: ty::ParamEnv<'tcx>,
original_values: &CanonicalVarValues<'tcx>,
query_result: &Canonical<'tcx, QueryResult<'tcx, R>>,
) -> Vec<QueryRegionConstraint<'tcx>>
output_query_region_constraints: &mut Vec<QueryRegionConstraint<'tcx>>,
) -> InferResult<'tcx, R>
where
R: Debug + TypeFoldable<'tcx>,
{
@ -210,52 +210,59 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
// Compute `QueryRegionConstraint` values that unify each of
// the original values `v_o` that was canonicalized into a
// variable...
let qrc_from_unify = original_values.var_values.iter_enumerated().flat_map(
|(index, original_value)| {
// ...with the value `v_r` of that variable from the query.
let result_value =
query_result
.substitute_projected(self.tcx, &result_subst, |v| &v.var_values[index]);
match (original_value.unpack(), result_value.unpack()) {
(
UnpackedKind::Lifetime(ty::ReErased),
UnpackedKind::Lifetime(ty::ReErased),
) => {
// no action needed
Either::Left(None.into_iter())
}
let mut obligations = vec![];
(UnpackedKind::Lifetime(v_o), UnpackedKind::Lifetime(v_r)) => {
// To make `v_o = v_r`, we emit `v_o: v_r` and `v_r: v_o`.
Either::Right(
once(ty::OutlivesPredicate(v_o.into(), v_r))
.chain(once(ty::OutlivesPredicate(v_r.into(), v_o)))
.map(ty::Binder::dummy),
)
}
for (index, original_value) in original_values.var_values.iter_enumerated() {
// ...with the value `v_r` of that variable from the query.
let result_value = query_result
.substitute_projected(self.tcx, &result_subst, |v| &v.var_values[index]);
match (original_value.unpack(), result_value.unpack()) {
(UnpackedKind::Lifetime(ty::ReErased), UnpackedKind::Lifetime(ty::ReErased)) => {
// no action needed
}
(UnpackedKind::Type(_), _) | (_, UnpackedKind::Type(_)) => {
// in NLL queries, we do not expect `type` results.
bug!(
"unexpected type in NLL query: cannot unify {:?} and {:?}",
original_value,
result_value,
);
(UnpackedKind::Lifetime(v_o), UnpackedKind::Lifetime(v_r)) => {
// To make `v_o = v_r`, we emit `v_o: v_r` and `v_r: v_o`.
if v_o != v_r {
output_query_region_constraints
.push(ty::Binder::dummy(ty::OutlivesPredicate(v_o.into(), v_r)));
output_query_region_constraints
.push(ty::Binder::dummy(ty::OutlivesPredicate(v_r.into(), v_o)));
}
}
},
);
(UnpackedKind::Type(v1), UnpackedKind::Type(v2)) => {
let ok = self.at(cause, param_env).eq(v1, v2)?;
obligations.extend(ok.into_obligations());
}
_ => {
bug!(
"kind mismatch, cannot unify {:?} and {:?}",
original_value,
result_value
);
}
}
}
// ...also include the other query region constraints from the query.
let qrc_from_result = query_result.value.region_constraints.iter().map(|r_c| {
r_c.map_bound(|ty::OutlivesPredicate(k1, r2)| {
output_query_region_constraints.reserve(query_result.value.region_constraints.len());
for r_c in query_result.value.region_constraints.iter() {
output_query_region_constraints.push(r_c.map_bound(|ty::OutlivesPredicate(k1, r2)| {
let k1 = substitute_value(self.tcx, &result_subst, &k1);
let r2 = substitute_value(self.tcx, &result_subst, &r2);
ty::OutlivesPredicate(k1, r2)
})
});
}));
}
qrc_from_unify.chain(qrc_from_result).collect()
let user_result: R =
query_result.substitute_projected(self.tcx, &result_subst, |q_r| &q_r.value);
Ok(InferOk {
value: user_result,
obligations,
})
}
/// Given the original values and the (canonicalized) result from

View File

@ -9,9 +9,14 @@
// except according to those terms.
use infer::{InferCtxt, InferOk};
use traits::query::Fallible;
use ty::TyCtxt;
use std::fmt;
use traits::query::Fallible;
use infer::canonical::query_result;
use infer::canonical::QueryRegionConstraint;
use std::rc::Rc;
use syntax::codemap::DUMMY_SP;
use traits::{ObligationCause, TraitEngine};
pub struct CustomTypeOp<F, G> {
closure: F,
@ -38,12 +43,18 @@ where
{
type Output = R;
fn trivial_noop(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::Output, Self> {
Err(self)
}
/// Processes the operation and all resulting obligations,
/// returning the final result along with any region constraints
/// (they will be given over to the NLL region solver).
fn fully_perform(
self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
) -> Fallible<(Self::Output, Option<Rc<Vec<QueryRegionConstraint<'tcx>>>>)> {
if cfg!(debug_assertions) {
info!("fully_perform({:?})", self);
}
fn perform(self, infcx: &InferCtxt<'_, 'gcx, 'tcx>) -> Fallible<InferOk<'tcx, R>> {
Ok((self.closure)(infcx)?)
scrape_region_constraints(infcx, || Ok((self.closure)(infcx)?))
}
}
@ -55,3 +66,35 @@ where
write!(f, "{}", (self.description)())
}
}
/// Executes `op` and then scrapes out all the "old style" region
/// constraints that result, creating query-region-constraints.
fn scrape_region_constraints<'gcx, 'tcx, R>(
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
op: impl FnOnce() -> Fallible<InferOk<'tcx, R>>,
) -> Fallible<(R, Option<Rc<Vec<QueryRegionConstraint<'tcx>>>>)> {
let mut fulfill_cx = TraitEngine::new(infcx.tcx);
let dummy_body_id = ObligationCause::dummy().body_id;
let InferOk { value, obligations } = infcx.commit_if_ok(|_| op())?;
debug_assert!(obligations.iter().all(|o| o.cause.body_id == dummy_body_id));
fulfill_cx.register_predicate_obligations(infcx, obligations);
if let Err(e) = fulfill_cx.select_all_or_error(infcx) {
infcx.tcx.sess.diagnostic().delay_span_bug(
DUMMY_SP,
&format!("errors selecting obligation during MIR typeck: {:?}", e),
);
}
let region_obligations = infcx.take_registered_region_obligations();
let region_constraint_data = infcx.take_and_reset_region_constraints();
let outlives =
query_result::make_query_outlives(infcx.tcx, region_obligations, &region_constraint_data);
if outlives.is_empty() {
Ok((value, None))
} else {
Ok((value, Some(Rc::new(outlives))))
}
}

View File

@ -10,7 +10,7 @@
use infer::canonical::{Canonical, CanonicalizedQueryResult, QueryResult};
use traits::query::Fallible;
use ty::{self, ParamEnv, Ty, TyCtxt};
use ty::{ParamEnv, Ty, TyCtxt};
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Eq<'tcx> {
@ -29,7 +29,7 @@ impl<'gcx: 'tcx, 'tcx> super::QueryTypeOp<'gcx, 'tcx> for Eq<'tcx> {
type QueryKey = Self;
type QueryResult = ();
fn trivial_noop(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::QueryResult, Self> {
fn prequery(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::QueryResult, Self> {
if self.a == self.b {
Ok(())
} else {
@ -37,12 +37,8 @@ impl<'gcx: 'tcx, 'tcx> super::QueryTypeOp<'gcx, 'tcx> for Eq<'tcx> {
}
}
fn into_query_key(self) -> Self {
self
}
fn param_env(&self) -> ty::ParamEnv<'tcx> {
self.param_env
fn param_env(key: &Self::QueryKey) -> ParamEnv<'tcx> {
key.param_env
}
fn perform_query(

View File

@ -8,16 +8,14 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use infer::canonical::query_result;
use infer::canonical::{
Canonical, Canonicalized, CanonicalizedQueryResult, QueryRegionConstraint, QueryResult,
};
use infer::{InferCtxt, InferOk};
use std::fmt;
use std::rc::Rc;
use syntax::codemap::DUMMY_SP;
use traits::query::Fallible;
use traits::{ObligationCause, TraitEngine};
use traits::ObligationCause;
use ty::fold::TypeFoldable;
use ty::{Lift, ParamEnv, TyCtxt};
@ -31,89 +29,25 @@ pub mod subtype;
pub trait TypeOp<'gcx, 'tcx>: Sized + fmt::Debug {
type Output;
/// Micro-optimization: returns `Ok(x)` if we can trivially
/// produce the output, else returns `Err(self)` back.
fn trivial_noop(self, tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::Output, Self>;
/// Given an infcx, performs **the kernel** of the operation: this does the
/// key action and then, optionally, returns a set of obligations which must be proven.
///
/// This method is not meant to be invoked directly: instead, one
/// should use `fully_perform`, which will take those resulting
/// obligations and prove them, and then process the combined
/// results into region obligations which are returned.
fn perform(
self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
) -> Fallible<InferOk<'tcx, Self::Output>>;
/// Processes the operation and all resulting obligations,
/// returning the final result along with any region constraints
/// (they will be given over to the NLL region solver).
fn fully_perform(
self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
) -> Fallible<(Self::Output, Option<Rc<Vec<QueryRegionConstraint<'tcx>>>>)> {
match self.trivial_noop(infcx.tcx) {
Ok(r) => Ok((r, None)),
Err(op) => op.fully_perform_nontrivial(infcx),
}
}
/// Helper for `fully_perform` that handles the nontrivial cases.
#[inline(never)] // just to help with profiling
fn fully_perform_nontrivial(
self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
) -> Fallible<(Self::Output, Option<Rc<Vec<QueryRegionConstraint<'tcx>>>>)> {
if cfg!(debug_assertions) {
info!(
"fully_perform_op_and_get_region_constraint_data({:?})",
self
);
}
let mut fulfill_cx = TraitEngine::new(infcx.tcx);
let dummy_body_id = ObligationCause::dummy().body_id;
let InferOk { value, obligations } = infcx.commit_if_ok(|_| self.perform(infcx))?;
debug_assert!(obligations.iter().all(|o| o.cause.body_id == dummy_body_id));
fulfill_cx.register_predicate_obligations(infcx, obligations);
if let Err(e) = fulfill_cx.select_all_or_error(infcx) {
infcx.tcx.sess.diagnostic().delay_span_bug(
DUMMY_SP,
&format!("errors selecting obligation during MIR typeck: {:?}", e),
);
}
let region_obligations = infcx.take_registered_region_obligations();
let region_constraint_data = infcx.take_and_reset_region_constraints();
let outlives = query_result::make_query_outlives(
infcx.tcx,
region_obligations,
&region_constraint_data,
);
if outlives.is_empty() {
Ok((value, None))
} else {
Ok((value, Some(Rc::new(outlives))))
}
}
) -> Fallible<(Self::Output, Option<Rc<Vec<QueryRegionConstraint<'tcx>>>>)>;
}
pub trait QueryTypeOp<'gcx: 'tcx, 'tcx>: fmt::Debug + Sized {
type QueryKey: TypeFoldable<'tcx> + Lift<'gcx>;
type QueryResult: TypeFoldable<'tcx> + Lift<'gcx>;
/// Micro-optimization: returns `Ok(x)` if we can trivially
/// produce the output, else returns `Err(self)` back.
fn trivial_noop(self, tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::QueryResult, Self>;
/// Either converts `self` directly into a `QueryResult` (for
/// simple cases) or into a `QueryKey` (for more complex cases
/// where we actually have work to do).
fn prequery(self, tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::QueryResult, Self::QueryKey>;
fn into_query_key(self) -> Self::QueryKey;
fn param_env(&self) -> ParamEnv<'tcx>;
fn param_env(key: &Self::QueryKey) -> ParamEnv<'tcx>;
fn perform_query(
tcx: TyCtxt<'_, 'gcx, 'tcx>,
@ -130,6 +64,51 @@ pub trait QueryTypeOp<'gcx: 'tcx, 'tcx>: fmt::Debug + Sized {
fn upcast_result(
lifted_query_result: &'a CanonicalizedQueryResult<'gcx, Self::QueryResult>,
) -> &'a Canonical<'tcx, QueryResult<'tcx, Self::QueryResult>>;
fn fully_perform_into(
self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
output_query_region_constraints: &mut Vec<QueryRegionConstraint<'tcx>>,
) -> Fallible<Self::QueryResult> {
match QueryTypeOp::prequery(self, infcx.tcx) {
Ok(result) => Ok(result),
Err(query_key) => {
// FIXME(#33684) -- We need to use
// `canonicalize_hr_query_hack` here because of things
// like the subtype query, which go awry around
// `'static` otherwise.
let (canonical_self, canonical_var_values) =
infcx.canonicalize_hr_query_hack(&query_key);
let canonical_result = Self::perform_query(infcx.tcx, canonical_self)?;
let canonical_result = Self::upcast_result(&canonical_result);
let param_env = Self::param_env(&query_key);
let InferOk { value, obligations } = infcx
.instantiate_nll_query_result_and_region_obligations(
&ObligationCause::dummy(),
param_env,
&canonical_var_values,
canonical_result,
output_query_region_constraints,
)?;
// Typically, instantiating NLL query results does not
// create obligations. However, in some cases there
// are unresolved type variables, and unify them *can*
// create obligations. In that case, we have to go
// fulfill them. We do this via a (recursive) query.
for obligation in obligations {
let () = prove_predicate::ProvePredicate::new(
obligation.param_env,
obligation.predicate,
).fully_perform_into(infcx, output_query_region_constraints)?;
}
Ok(value)
}
}
}
}
impl<'gcx: 'tcx, 'tcx, Q> TypeOp<'gcx, 'tcx> for Q
@ -138,38 +117,21 @@ where
{
type Output = Q::QueryResult;
fn trivial_noop(self, tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::Output, Self> {
QueryTypeOp::trivial_noop(self, tcx)
}
fn perform(
fn fully_perform(
self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
) -> Fallible<InferOk<'tcx, Self::Output>> {
let param_env = self.param_env();
) -> Fallible<(Self::Output, Option<Rc<Vec<QueryRegionConstraint<'tcx>>>>)> {
let mut qrc = vec![];
let r = Q::fully_perform_into(self, infcx, &mut qrc)?;
// FIXME(#33684) -- We need to use
// `canonicalize_hr_query_hack` here because of things like
// the subtype query, which go awry around `'static`
// otherwise.
let query_key = self.into_query_key();
let (canonical_self, canonical_var_values) = infcx.canonicalize_hr_query_hack(&query_key);
let canonical_result = Q::perform_query(infcx.tcx, canonical_self)?;
// Promote the final query-region-constraints into a
// (optional) ref-counted vector:
let opt_qrc = if qrc.is_empty() {
None
} else {
Some(Rc::new(qrc))
};
// FIXME: This is not the most efficient setup. The
// `instantiate_query_result_and_region_obligations` basically
// takes the `QueryRegionConstraint` values that we ultimately
// want to use and converts them into obligations. We return
// those to our caller, which will convert them into AST
// region constraints; we then convert *those* back into
// `QueryRegionConstraint` and ultimately into NLL
// constraints. We should cut out the middleman but that will
// take a bit of refactoring.
Ok(infcx.instantiate_query_result_and_region_obligations(
&ObligationCause::dummy(),
param_env,
&canonical_var_values,
Q::upcast_result(&canonical_result),
)?)
Ok((r, opt_qrc))
}
}

View File

@ -36,7 +36,7 @@ where
type QueryKey = Self;
type QueryResult = T;
fn trivial_noop(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<T, Self> {
fn prequery(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<T, Self> {
if !self.value.has_projections() {
Ok(self.value)
} else {
@ -44,12 +44,8 @@ where
}
}
fn into_query_key(self) -> Self {
self
}
fn param_env(&self) -> ParamEnv<'tcx> {
self.param_env
fn param_env(key: &Self::QueryKey) -> ParamEnv<'tcx> {
key.param_env
}
fn perform_query(

View File

@ -36,20 +36,16 @@ where
type QueryKey = ParamEnvAnd<'tcx, Ty<'tcx>>;
type QueryResult = DropckOutlivesResult<'tcx>;
fn trivial_noop(self, tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::QueryResult, Self> {
fn prequery(self, tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::QueryResult, Self::QueryKey> {
if trivial_dropck_outlives(tcx, self.dropped_ty) {
Ok(DropckOutlivesResult::default())
} else {
Err(self)
Err(self.param_env.and(self.dropped_ty))
}
}
fn param_env(&self) -> ParamEnv<'tcx> {
self.param_env
}
fn into_query_key(self) -> Self::QueryKey {
self.param_env.and(self.dropped_ty)
fn param_env(key: &Self::QueryKey) -> ParamEnv<'tcx> {
key.param_env
}
fn perform_query(

View File

@ -31,16 +31,12 @@ impl<'gcx: 'tcx, 'tcx> super::QueryTypeOp<'gcx, 'tcx> for ProvePredicate<'tcx> {
type QueryKey = Self;
type QueryResult = ();
fn trivial_noop(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::QueryResult, Self> {
fn prequery(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<Self::QueryResult, Self::QueryKey> {
Err(self)
}
fn into_query_key(self) -> Self {
self
}
fn param_env(&self) -> ParamEnv<'tcx> {
self.param_env
fn param_env(key: &Self::QueryKey) -> ParamEnv<'tcx> {
key.param_env
}
fn perform_query(

View File

@ -33,7 +33,7 @@ impl<'gcx: 'tcx, 'tcx> super::QueryTypeOp<'gcx, 'tcx> for Subtype<'tcx> {
type QueryKey = Self;
type QueryResult = ();
fn trivial_noop(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<(), Self> {
fn prequery(self, _tcx: TyCtxt<'_, 'gcx, 'tcx>) -> Result<(), Self::QueryKey> {
if self.sub == self.sup {
Ok(())
} else {
@ -41,12 +41,8 @@ impl<'gcx: 'tcx, 'tcx> super::QueryTypeOp<'gcx, 'tcx> for Subtype<'tcx> {
}
}
fn into_query_key(self) -> Self {
self
}
fn param_env(&self) -> ParamEnv<'tcx> {
self.param_env
fn param_env(key: &Self::QueryKey) -> ParamEnv<'tcx> {
key.param_env
}
fn perform_query(