use chalk_derive::FallibleTypeFolder;
use chalk_ir::{
fold::{TypeFoldable, TypeFolder},
interner::{HasInterner, Interner},
Binders, BoundVar, Const, ConstData, ConstValue, DebruijnIndex, Lifetime, LifetimeData, Ty,
TyKind, TyVariableKind, VariableKind, VariableKinds,
};
use rustc_hash::FxHashMap;
#[derive(FallibleTypeFolder)]
pub struct Generalize<I: Interner> {
binders: Vec<VariableKind<I>>,
mapping: FxHashMap<BoundVar, usize>,
interner: I,
}
impl<I: Interner> Generalize<I> {
pub fn apply<T>(interner: I, value: T) -> Binders<T>
where
T: HasInterner<Interner = I> + TypeFoldable<I>,
{
let mut generalize = Generalize {
binders: Vec::new(),
mapping: FxHashMap::default(),
interner,
};
let value = value
.try_fold_with(&mut generalize, DebruijnIndex::INNERMOST)
.unwrap();
Binders::new(
VariableKinds::from_iter(interner, generalize.binders),
value,
)
}
}
impl<I: Interner> TypeFolder<I> for Generalize<I> {
fn as_dyn(&mut self) -> &mut dyn TypeFolder<I> {
self
}
fn fold_free_var_ty(&mut self, bound_var: BoundVar, outer_binder: DebruijnIndex) -> Ty<I> {
let binder_vec = &mut self.binders;
let new_index = self.mapping.entry(bound_var).or_insert_with(|| {
let i = binder_vec.len();
binder_vec.push(VariableKind::Ty(TyVariableKind::General));
i
});
let new_var = BoundVar::new(outer_binder, *new_index);
TyKind::BoundVar(new_var).intern(TypeFolder::interner(self))
}
fn fold_free_var_const(
&mut self,
ty: Ty<I>,
bound_var: BoundVar,
outer_binder: DebruijnIndex,
) -> Const<I> {
let binder_vec = &mut self.binders;
let new_index = self.mapping.entry(bound_var).or_insert_with(|| {
let i = binder_vec.len();
binder_vec.push(VariableKind::Const(ty.clone()));
i
});
let new_var = BoundVar::new(outer_binder, *new_index);
ConstData {
ty,
value: ConstValue::BoundVar(new_var),
}
.intern(TypeFolder::interner(self))
}
fn fold_free_var_lifetime(
&mut self,
bound_var: BoundVar,
outer_binder: DebruijnIndex,
) -> Lifetime<I> {
let binder_vec = &mut self.binders;
let new_index = self.mapping.entry(bound_var).or_insert_with(|| {
let i = binder_vec.len();
binder_vec.push(VariableKind::Lifetime);
i
});
let new_var = BoundVar::new(outer_binder, *new_index);
LifetimeData::BoundVar(new_var).intern(TypeFolder::interner(self))
}
fn interner(&self) -> I {
self.interner
}
}