summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_infer/src/infer/freshen.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_infer/src/infer/freshen.rs')
-rw-r--r--compiler/rustc_infer/src/infer/freshen.rs255
1 files changed, 255 insertions, 0 deletions
diff --git a/compiler/rustc_infer/src/infer/freshen.rs b/compiler/rustc_infer/src/infer/freshen.rs
new file mode 100644
index 000000000..84004d2b2
--- /dev/null
+++ b/compiler/rustc_infer/src/infer/freshen.rs
@@ -0,0 +1,255 @@
+//! Freshening is the process of replacing unknown variables with fresh types. The idea is that
+//! the type, after freshening, contains no inference variables but instead contains either a
+//! value for each variable or fresh "arbitrary" types wherever a variable would have been.
+//!
+//! Freshening is used primarily to get a good type for inserting into a cache. The result
+//! summarizes what the type inferencer knows "so far". The primary place it is used right now is
+//! in the trait matching algorithm, which needs to be able to cache whether an `impl` self type
+//! matches some other type X -- *without* affecting `X`. That means if that if the type `X` is in
+//! fact an unbound type variable, we want the match to be regarded as ambiguous, because depending
+//! on what type that type variable is ultimately assigned, the match may or may not succeed.
+//!
+//! To handle closures, freshened types also have to contain the signature and kind of any
+//! closure in the local inference context, as otherwise the cache key might be invalidated.
+//! The way this is done is somewhat hacky - the closure signature is appended to the substs,
+//! as well as the closure kind "encoded" as a type. Also, special handling is needed when
+//! the closure signature contains a reference to the original closure.
+//!
+//! Note that you should be careful not to allow the output of freshening to leak to the user in
+//! error messages or in any other form. Freshening is only really useful as an internal detail.
+//!
+//! Because of the manipulation required to handle closures, doing arbitrary operations on
+//! freshened types is not recommended. However, in addition to doing equality/hash
+//! comparisons (for caching), it is possible to do a `ty::_match` operation between
+//! 2 freshened types - this works even with the closure encoding.
+//!
+//! __An important detail concerning regions.__ The freshener also replaces *all* free regions with
+//! 'erased. The reason behind this is that, in general, we do not take region relationships into
+//! account when making type-overloaded decisions. This is important because of the design of the
+//! region inferencer, which is not based on unification but rather on accumulating and then
+//! solving a set of constraints. In contrast, the type inferencer assigns a value to each type
+//! variable only once, and it does so as soon as it can, so it is reasonable to ask what the type
+//! inferencer knows "so far".
+use super::InferCtxt;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::infer::unify_key::ToType;
+use rustc_middle::ty::fold::TypeFolder;
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitable};
+use std::collections::hash_map::Entry;
+
+pub struct TypeFreshener<'a, 'tcx> {
+ infcx: &'a InferCtxt<'a, 'tcx>,
+ ty_freshen_count: u32,
+ const_freshen_count: u32,
+ ty_freshen_map: FxHashMap<ty::InferTy, Ty<'tcx>>,
+ const_freshen_map: FxHashMap<ty::InferConst<'tcx>, ty::Const<'tcx>>,
+ keep_static: bool,
+}
+
+impl<'a, 'tcx> TypeFreshener<'a, 'tcx> {
+ pub fn new(infcx: &'a InferCtxt<'a, 'tcx>, keep_static: bool) -> TypeFreshener<'a, 'tcx> {
+ TypeFreshener {
+ infcx,
+ ty_freshen_count: 0,
+ const_freshen_count: 0,
+ ty_freshen_map: Default::default(),
+ const_freshen_map: Default::default(),
+ keep_static,
+ }
+ }
+
+ fn freshen_ty<F>(
+ &mut self,
+ opt_ty: Option<Ty<'tcx>>,
+ key: ty::InferTy,
+ freshener: F,
+ ) -> Ty<'tcx>
+ where
+ F: FnOnce(u32) -> ty::InferTy,
+ {
+ if let Some(ty) = opt_ty {
+ return ty.fold_with(self);
+ }
+
+ match self.ty_freshen_map.entry(key) {
+ Entry::Occupied(entry) => *entry.get(),
+ Entry::Vacant(entry) => {
+ let index = self.ty_freshen_count;
+ self.ty_freshen_count += 1;
+ let t = self.infcx.tcx.mk_ty_infer(freshener(index));
+ entry.insert(t);
+ t
+ }
+ }
+ }
+
+ fn freshen_const<F>(
+ &mut self,
+ opt_ct: Option<ty::Const<'tcx>>,
+ key: ty::InferConst<'tcx>,
+ freshener: F,
+ ty: Ty<'tcx>,
+ ) -> ty::Const<'tcx>
+ where
+ F: FnOnce(u32) -> ty::InferConst<'tcx>,
+ {
+ if let Some(ct) = opt_ct {
+ return ct.fold_with(self);
+ }
+
+ match self.const_freshen_map.entry(key) {
+ Entry::Occupied(entry) => *entry.get(),
+ Entry::Vacant(entry) => {
+ let index = self.const_freshen_count;
+ self.const_freshen_count += 1;
+ let ct = self.infcx.tcx.mk_const_infer(freshener(index), ty);
+ entry.insert(ct);
+ ct
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> TypeFolder<'tcx> for TypeFreshener<'a, 'tcx> {
+ fn tcx<'b>(&'b self) -> TyCtxt<'tcx> {
+ self.infcx.tcx
+ }
+
+ fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
+ match *r {
+ ty::ReLateBound(..) => {
+ // leave bound regions alone
+ r
+ }
+
+ ty::ReEarlyBound(..)
+ | ty::ReFree(_)
+ | ty::ReVar(_)
+ | ty::RePlaceholder(..)
+ | ty::ReEmpty(_)
+ | ty::ReErased => {
+ // replace all free regions with 'erased
+ self.tcx().lifetimes.re_erased
+ }
+ ty::ReStatic => {
+ if self.keep_static {
+ r
+ } else {
+ self.tcx().lifetimes.re_erased
+ }
+ }
+ }
+ }
+
+ fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
+ if !t.needs_infer() && !t.has_erasable_regions() {
+ return t;
+ }
+
+ let tcx = self.infcx.tcx;
+
+ match *t.kind() {
+ ty::Infer(ty::TyVar(v)) => {
+ let opt_ty = self.infcx.inner.borrow_mut().type_variables().probe(v).known();
+ self.freshen_ty(opt_ty, ty::TyVar(v), ty::FreshTy)
+ }
+
+ ty::Infer(ty::IntVar(v)) => self.freshen_ty(
+ self.infcx
+ .inner
+ .borrow_mut()
+ .int_unification_table()
+ .probe_value(v)
+ .map(|v| v.to_type(tcx)),
+ ty::IntVar(v),
+ ty::FreshIntTy,
+ ),
+
+ ty::Infer(ty::FloatVar(v)) => self.freshen_ty(
+ self.infcx
+ .inner
+ .borrow_mut()
+ .float_unification_table()
+ .probe_value(v)
+ .map(|v| v.to_type(tcx)),
+ ty::FloatVar(v),
+ ty::FreshFloatTy,
+ ),
+
+ ty::Infer(ty::FreshTy(ct) | ty::FreshIntTy(ct) | ty::FreshFloatTy(ct)) => {
+ if ct >= self.ty_freshen_count {
+ bug!(
+ "Encountered a freshend type with id {} \
+ but our counter is only at {}",
+ ct,
+ self.ty_freshen_count
+ );
+ }
+ t
+ }
+
+ ty::Generator(..)
+ | ty::Bool
+ | ty::Char
+ | ty::Int(..)
+ | ty::Uint(..)
+ | ty::Float(..)
+ | ty::Adt(..)
+ | ty::Str
+ | ty::Error(_)
+ | ty::Array(..)
+ | ty::Slice(..)
+ | ty::RawPtr(..)
+ | ty::Ref(..)
+ | ty::FnDef(..)
+ | ty::FnPtr(_)
+ | ty::Dynamic(..)
+ | ty::Never
+ | ty::Tuple(..)
+ | ty::Projection(..)
+ | ty::Foreign(..)
+ | ty::Param(..)
+ | ty::Closure(..)
+ | ty::GeneratorWitness(..)
+ | ty::Opaque(..) => t.super_fold_with(self),
+
+ ty::Placeholder(..) | ty::Bound(..) => bug!("unexpected type {:?}", t),
+ }
+ }
+
+ fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
+ match ct.kind() {
+ ty::ConstKind::Infer(ty::InferConst::Var(v)) => {
+ let opt_ct = self
+ .infcx
+ .inner
+ .borrow_mut()
+ .const_unification_table()
+ .probe_value(v)
+ .val
+ .known();
+ self.freshen_const(opt_ct, ty::InferConst::Var(v), ty::InferConst::Fresh, ct.ty())
+ }
+ ty::ConstKind::Infer(ty::InferConst::Fresh(i)) => {
+ if i >= self.const_freshen_count {
+ bug!(
+ "Encountered a freshend const with id {} \
+ but our counter is only at {}",
+ i,
+ self.const_freshen_count,
+ );
+ }
+ ct
+ }
+
+ ty::ConstKind::Bound(..) | ty::ConstKind::Placeholder(_) => {
+ bug!("unexpected const {:?}", ct)
+ }
+
+ ty::ConstKind::Param(_)
+ | ty::ConstKind::Value(_)
+ | ty::ConstKind::Unevaluated(..)
+ | ty::ConstKind::Error(_) => ct.super_fold_with(self),
+ }
+ }
+}