1
Fork 0

implement (unused) matching solver

This commit is contained in:
Niko Matsakis 2022-06-15 05:55:05 -04:00
parent d203c13db2
commit c5ed318b22
7 changed files with 329 additions and 24 deletions

View file

@ -22,6 +22,8 @@ use rustc_middle::ty::{Region, RegionVid};
use rustc_span::Span;
use std::fmt;
use super::outlives::test_type_match;
/// This function performs lexical region resolution given a complete
/// set of constraints and variable origins. It performs a fixed-point
/// iteration to find region values which satisfy all constraints,
@ -29,12 +31,13 @@ use std::fmt;
/// all the variables as well as a set of errors that must be reported.
#[instrument(level = "debug", skip(region_rels, var_infos, data))]
pub(crate) fn resolve<'tcx>(
param_env: ty::ParamEnv<'tcx>,
region_rels: &RegionRelations<'_, 'tcx>,
var_infos: VarInfos,
data: RegionConstraintData<'tcx>,
) -> (LexicalRegionResolutions<'tcx>, Vec<RegionResolutionError<'tcx>>) {
let mut errors = vec![];
let mut resolver = LexicalResolver { region_rels, var_infos, data };
let mut resolver = LexicalResolver { param_env, region_rels, var_infos, data };
let values = resolver.infer_variable_values(&mut errors);
(values, errors)
}
@ -100,6 +103,7 @@ struct RegionAndOrigin<'tcx> {
type RegionGraph<'tcx> = Graph<(), Constraint<'tcx>>;
struct LexicalResolver<'cx, 'tcx> {
param_env: ty::ParamEnv<'tcx>,
region_rels: &'cx RegionRelations<'cx, 'tcx>,
var_infos: VarInfos,
data: RegionConstraintData<'tcx>,
@ -823,6 +827,21 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
&& self.bound_is_met(&VerifyBound::OutlivedBy(*r), var_values, generic_ty, min)
}
VerifyBound::IfEqBound(verify_if_eq_b) => {
match test_type_match::extract_verify_if_eq_bound(
self.tcx(),
self.param_env,
verify_if_eq_b,
generic_ty,
) {
Some(r) => {
self.bound_is_met(&VerifyBound::OutlivedBy(r), var_values, generic_ty, min)
}
None => false,
}
}
VerifyBound::OutlivedBy(r) => {
self.sub_concrete_regions(min, var_values.normalize(self.tcx(), *r))
}

View file

@ -1290,7 +1290,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
&RegionRelations::new(self.tcx, region_context, outlives_env.free_region_map());
let (lexical_region_resolutions, errors) =
lexical_region_resolve::resolve(region_rels, var_infos, data);
lexical_region_resolve::resolve(outlives_env.param_env, region_rels, var_infos, data);
let old_value = self.lexical_region_resolutions.replace(Some(lexical_region_resolutions));
assert!(old_value.is_none());

View file

@ -3,6 +3,7 @@
pub mod components;
pub mod env;
pub mod obligations;
pub mod test_type_match;
pub mod verify;
use rustc_middle::traits::query::OutlivesBound;

View file

@ -0,0 +1,179 @@
use std::collections::hash_map::Entry;
use rustc_data_structures::fx::FxHashMap;
use rustc_middle::ty::TypeFoldable;
use rustc_middle::ty::{
self,
error::TypeError,
relate::{self, Relate, RelateResult, TypeRelation},
Ty, TyCtxt,
};
use crate::infer::region_constraints::VerifyIfEq;
/// Given a "verify-if-eq" type test like:
///
/// exists<'a...> {
/// verify_if_eq(some_type, bound_region)
/// }
///
/// and the type `test_ty` that the type test is being tested against,
/// returns:
///
/// * `None` if `some_type` cannot be made equal to `test_ty`,
/// no matter the values of the variables in `exists`.
/// * `Some(r)` with a suitable bound (typically the value of `bound_region`, modulo
/// any bound existential variables, which will be substituted) for the
/// type under test.
///
/// NB: This function uses a simplistic, syntactic version of type equality.
/// In other words, it may spuriously return `None` even if the type-under-test
/// is in fact equal to `some_type`. In practice, though, this is used on types
/// that are either projections like `T::Item` or `T` and it works fine, but it
/// could have trouble when complex types with higher-ranked binders and the
/// like are used. This is a particular challenge since this function is invoked
/// very late in inference and hence cannot make use of the normal inference
/// machinery.
pub fn extract_verify_if_eq_bound<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
verify_if_eq_b: &ty::Binder<'tcx, VerifyIfEq<'tcx>>,
test_ty: Ty<'tcx>,
) -> Option<ty::Region<'tcx>> {
assert!(!verify_if_eq_b.has_escaping_bound_vars());
let mut m = Match::new(tcx, param_env);
let verify_if_eq = verify_if_eq_b.skip_binder();
m.relate(verify_if_eq.ty, test_ty).ok()?;
if let ty::RegionKind::ReLateBound(depth, br) = verify_if_eq.bound.kind() {
assert!(depth == ty::INNERMOST);
match m.map.get(&br) {
Some(&r) => Some(r),
None => {
// If there is no mapping, then this region is unconstrained.
// In that case, we escalate to `'static`.
Some(tcx.lifetimes.re_static)
}
}
} else {
// The region does not contain any inference variables.
Some(verify_if_eq.bound)
}
}
struct Match<'tcx> {
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
pattern_depth: ty::DebruijnIndex,
map: FxHashMap<ty::BoundRegion, ty::Region<'tcx>>,
}
impl<'tcx> Match<'tcx> {
fn new(tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Match<'tcx> {
Match { tcx, param_env, pattern_depth: ty::INNERMOST, map: FxHashMap::default() }
}
}
impl<'tcx> Match<'tcx> {
/// Creates the "Error" variant that signals "no match".
fn no_match<T>(&self) -> RelateResult<'tcx, T> {
Err(TypeError::Mismatch)
}
/// Binds the pattern variable `br` to `value`; returns an `Err` if the pattern
/// is already bound to a different value.
fn bind(
&mut self,
br: ty::BoundRegion,
value: ty::Region<'tcx>,
) -> RelateResult<'tcx, ty::Region<'tcx>> {
match self.map.entry(br) {
Entry::Occupied(entry) => {
if *entry.get() == value {
Ok(value)
} else {
self.no_match()
}
}
Entry::Vacant(entry) => {
entry.insert(value);
Ok(value)
}
}
}
}
impl<'tcx> TypeRelation<'tcx> for Match<'tcx> {
fn tag(&self) -> &'static str {
"Match"
}
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn param_env(&self) -> ty::ParamEnv<'tcx> {
self.param_env
}
fn a_is_expected(&self) -> bool {
true
} // irrelevant
fn relate_with_variance<T: Relate<'tcx>>(
&mut self,
_: ty::Variance,
_: ty::VarianceDiagInfo<'tcx>,
a: T,
b: T,
) -> RelateResult<'tcx, T> {
self.relate(a, b)
}
#[instrument(skip(self), level = "debug")]
fn regions(
&mut self,
pattern: ty::Region<'tcx>,
value: ty::Region<'tcx>,
) -> RelateResult<'tcx, ty::Region<'tcx>> {
if let ty::RegionKind::ReLateBound(depth, br) = pattern.kind() && depth == self.pattern_depth {
self.bind(br, pattern)
} else if pattern == value {
Ok(pattern)
} else {
self.no_match()
}
}
fn tys(&mut self, pattern: Ty<'tcx>, value: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
if pattern == value {
return Ok(pattern);
} else {
relate::super_relate_tys(self, pattern, value)
}
}
fn consts(
&mut self,
pattern: ty::Const<'tcx>,
value: ty::Const<'tcx>,
) -> RelateResult<'tcx, ty::Const<'tcx>> {
debug!("{}.consts({:?}, {:?})", self.tag(), pattern, value);
if pattern == value {
return Ok(pattern);
} else {
relate::super_relate_consts(self, pattern, value)
}
}
fn binders<T>(
&mut self,
pattern: ty::Binder<'tcx, T>,
value: ty::Binder<'tcx, T>,
) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
where
T: Relate<'tcx>,
{
self.pattern_depth.shift_in(1);
let result = Ok(pattern.rebind(self.relate(pattern.skip_binder(), value.skip_binder())?));
self.pattern_depth.shift_out(1);
result
}
}

View file

@ -226,6 +226,8 @@ pub enum VerifyBound<'tcx> {
/// (after inference), and `'a: min`, then `G: min`.
IfEq(Ty<'tcx>, Region<'tcx>),
IfEqBound(ty::Binder<'tcx, VerifyIfEq<'tcx>>),
/// Given a region `R`, expands to the function:
///
/// ```ignore (pseudo-rust)
@ -267,6 +269,49 @@ pub enum VerifyBound<'tcx> {
AllBounds(Vec<VerifyBound<'tcx>>),
}
/// Given a kind K and a bound B, expands to a function like the
/// following, where `G` is the generic for which this verify
/// bound was created:
///
/// ```ignore (pseudo-rust)
/// fn(min) -> bool {
/// if G == K {
/// B(min)
/// } else {
/// false
/// }
/// }
/// ```
///
/// In other words, if the generic `G` that we are checking is
/// equal to `K`, then check the associated verify bound
/// (otherwise, false).
///
/// This is used when we have something in the environment that
/// may or may not be relevant, depending on the region inference
/// results. For example, we may have `where <T as
/// Trait<'a>>::Item: 'b` in our where-clauses. If we are
/// generating the verify-bound for `<T as Trait<'0>>::Item`, then
/// this where-clause is only relevant if `'0` winds up inferred
/// to `'a`.
///
/// So we would compile to a verify-bound like
///
/// ```ignore (illustrative)
/// IfEq(<T as Trait<'a>>::Item, AnyRegion('a))
/// ```
///
/// meaning, if the subject G is equal to `<T as Trait<'a>>::Item`
/// (after inference), and `'a: min`, then `G: min`.
#[derive(Debug, Copy, Clone, TypeFoldable)]
pub struct VerifyIfEq<'tcx> {
/// Type which must match the generic `G`
pub ty: Ty<'tcx>,
/// Bound that applies if `ty` is equal.
pub bound: Region<'tcx>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct TwoRegions<'tcx> {
a: Region<'tcx>,
@ -761,6 +806,7 @@ impl<'tcx> VerifyBound<'tcx> {
pub fn must_hold(&self) -> bool {
match self {
VerifyBound::IfEq(..) => false,
VerifyBound::IfEqBound(..) => false,
VerifyBound::OutlivedBy(re) => re.is_static(),
VerifyBound::IsEmpty => false,
VerifyBound::AnyBound(bs) => bs.iter().any(|b| b.must_hold()),
@ -771,6 +817,7 @@ impl<'tcx> VerifyBound<'tcx> {
pub fn cannot_hold(&self) -> bool {
match self {
VerifyBound::IfEq(_, _) => false,
VerifyBound::IfEqBound(..) => false,
VerifyBound::IsEmpty => false,
VerifyBound::OutlivedBy(_) => false,
VerifyBound::AnyBound(bs) => bs.iter().all(|b| b.cannot_hold()),