Fix handling of the binders in dyn/impl Trait

We need to be more careful now when substituting bound variables (previously, we
didn't have anything that used bound variables except Chalk, so it was not a
problem).

This is obviously quite ad-hoc; Chalk has more infrastructure for handling this
in a principled way, which we maybe should adopt.
This commit is contained in:
Florian Diebold 2019-11-16 12:53:13 +01:00
parent 9c2a9a9a06
commit 351c29d859
4 changed files with 116 additions and 45 deletions

View File

@ -224,8 +224,8 @@ impl TypeWalk for ProjectionTy {
self.parameters.walk(f); self.parameters.walk(f);
} }
fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) {
self.parameters.walk_mut(f); self.parameters.walk_mut_binders(f, binders);
} }
} }
@ -291,6 +291,20 @@ pub enum Ty {
#[derive(Clone, PartialEq, Eq, Debug, Hash)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct Substs(Arc<[Ty]>); pub struct Substs(Arc<[Ty]>);
impl TypeWalk for Substs {
fn walk(&self, f: &mut impl FnMut(&Ty)) {
for t in self.0.iter() {
t.walk(f);
}
}
fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) {
for t in make_mut_slice(&mut self.0) {
t.walk_mut_binders(f, binders);
}
}
}
impl Substs { impl Substs {
pub fn empty() -> Substs { pub fn empty() -> Substs {
Substs(Arc::new([])) Substs(Arc::new([]))
@ -304,18 +318,6 @@ impl Substs {
Substs(self.0[..std::cmp::min(self.0.len(), n)].into()) Substs(self.0[..std::cmp::min(self.0.len(), n)].into())
} }
pub fn walk(&self, f: &mut impl FnMut(&Ty)) {
for t in self.0.iter() {
t.walk(f);
}
}
pub fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) {
for t in make_mut_slice(&mut self.0) {
t.walk_mut(f);
}
}
pub fn as_single(&self) -> &Ty { pub fn as_single(&self) -> &Ty {
if self.0.len() != 1 { if self.0.len() != 1 {
panic!("expected substs of len 1, got {:?}", self); panic!("expected substs of len 1, got {:?}", self);
@ -440,8 +442,8 @@ impl TypeWalk for TraitRef {
self.substs.walk(f); self.substs.walk(f);
} }
fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) {
self.substs.walk_mut(f); self.substs.walk_mut_binders(f, binders);
} }
} }
@ -491,10 +493,12 @@ impl TypeWalk for GenericPredicate {
} }
} }
fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) {
match self { match self {
GenericPredicate::Implemented(trait_ref) => trait_ref.walk_mut(f), GenericPredicate::Implemented(trait_ref) => trait_ref.walk_mut_binders(f, binders),
GenericPredicate::Projection(projection_pred) => projection_pred.walk_mut(f), GenericPredicate::Projection(projection_pred) => {
projection_pred.walk_mut_binders(f, binders)
}
GenericPredicate::Error => {} GenericPredicate::Error => {}
} }
} }
@ -544,9 +548,9 @@ impl TypeWalk for FnSig {
} }
} }
fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) {
for t in make_mut_slice(&mut self.params_and_return) { for t in make_mut_slice(&mut self.params_and_return) {
t.walk_mut(f); t.walk_mut_binders(f, binders);
} }
} }
} }
@ -671,7 +675,20 @@ impl Ty {
/// types, similar to Chalk's `Fold` trait. /// types, similar to Chalk's `Fold` trait.
pub trait TypeWalk { pub trait TypeWalk {
fn walk(&self, f: &mut impl FnMut(&Ty)); fn walk(&self, f: &mut impl FnMut(&Ty));
fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)); fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) {
self.walk_mut_binders(&mut |ty, _binders| f(ty), 0);
}
/// Walk the type, counting entered binders.
///
/// `Ty::Bound` variables use DeBruijn indexing, which means that 0 refers
/// to the innermost binder, 1 to the next, etc.. So when we want to
/// substitute a certain bound variable, we can't just walk the whole type
/// and blindly replace each instance of a certain index; when we 'enter'
/// things that introduce new bound variables, we have to keep track of
/// that. Currently, the only thing that introduces bound variables on our
/// side are `Ty::Dyn` and `Ty::Opaque`, which each introduce a bound
/// variable for the self type.
fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize);
fn fold(mut self, f: &mut impl FnMut(Ty) -> Ty) -> Self fn fold(mut self, f: &mut impl FnMut(Ty) -> Ty) -> Self
where where
@ -700,14 +717,22 @@ pub trait TypeWalk {
} }
/// Substitutes `Ty::Bound` vars (as opposed to type parameters). /// Substitutes `Ty::Bound` vars (as opposed to type parameters).
fn subst_bound_vars(self, substs: &Substs) -> Self fn subst_bound_vars(mut self, substs: &Substs) -> Self
where where
Self: Sized, Self: Sized,
{ {
self.fold(&mut |ty| match ty { self.walk_mut_binders(
Ty::Bound(idx) => substs.get(idx as usize).cloned().unwrap_or_else(|| Ty::Bound(idx)), &mut |ty, binders| match ty {
ty => ty, &mut Ty::Bound(idx) => {
}) if idx as usize >= binders && (idx as usize - binders) < substs.len() {
*ty = substs.0[idx as usize - binders].clone();
}
}
_ => {}
},
0,
);
self
} }
/// Shifts up `Ty::Bound` vars by `n`. /// Shifts up `Ty::Bound` vars by `n`.
@ -748,22 +773,22 @@ impl TypeWalk for Ty {
f(self); f(self);
} }
fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) {
match self { match self {
Ty::Apply(a_ty) => { Ty::Apply(a_ty) => {
a_ty.parameters.walk_mut(f); a_ty.parameters.walk_mut_binders(f, binders);
} }
Ty::Projection(p_ty) => { Ty::Projection(p_ty) => {
p_ty.parameters.walk_mut(f); p_ty.parameters.walk_mut_binders(f, binders);
} }
Ty::Dyn(predicates) | Ty::Opaque(predicates) => { Ty::Dyn(predicates) | Ty::Opaque(predicates) => {
for p in make_mut_slice(predicates) { for p in make_mut_slice(predicates) {
p.walk_mut(f); p.walk_mut_binders(f, binders + 1);
} }
} }
Ty::Param { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {} Ty::Param { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
} }
f(self); f(self, binders);
} }
} }

View File

@ -134,17 +134,19 @@ where
} }
impl<T> Canonicalized<T> { impl<T> Canonicalized<T> {
pub fn decanonicalize_ty(&self, ty: Ty) -> Ty { pub fn decanonicalize_ty(&self, mut ty: Ty) -> Ty {
ty.fold(&mut |ty| match ty { ty.walk_mut_binders(
Ty::Bound(idx) => { &mut |ty, binders| match ty {
if (idx as usize) < self.free_vars.len() { &mut Ty::Bound(idx) => {
Ty::Infer(self.free_vars[idx as usize]) if idx as usize >= binders && (idx as usize - binders) < self.free_vars.len() {
} else { *ty = Ty::Infer(self.free_vars[idx as usize - binders]);
Ty::Bound(idx) }
} }
} _ => {}
ty => ty, },
}) 0,
);
ty
} }
pub fn apply_solution( pub fn apply_solution(

View File

@ -4184,6 +4184,49 @@ fn test<T: Trait<Type = u32>>(x: T, y: impl Trait<Type = i64>) {
); );
} }
#[test]
fn impl_trait_assoc_binding_projection_bug() {
let (db, pos) = TestDB::with_position(
r#"
//- /main.rs crate:main deps:std
pub trait Language {
type Kind;
}
pub enum RustLanguage {}
impl Language for RustLanguage {
type Kind = SyntaxKind;
}
struct SyntaxNode<L> {}
fn foo() -> impl Iterator<Item = SyntaxNode<RustLanguage>> {}
trait Clone {
fn clone(&self) -> Self;
}
fn api_walkthrough() {
for node in foo() {
node.clone()<|>;
}
}
//- /std.rs crate:std
#[prelude_import] use iter::*;
mod iter {
trait IntoIterator {
type Item;
}
trait Iterator {
type Item;
}
impl<T: Iterator> IntoIterator for T {
type Item = <T as Iterator>::Item;
}
}
"#,
);
assert_eq!("{unknown}", type_at_pos(&db, pos));
}
#[test] #[test]
fn projection_eq_within_chalk() { fn projection_eq_within_chalk() {
// std::env::set_var("CHALK_DEBUG", "1"); // std::env::set_var("CHALK_DEBUG", "1");

View File

@ -165,9 +165,9 @@ impl TypeWalk for ProjectionPredicate {
self.ty.walk(f); self.ty.walk(f);
} }
fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) {
self.projection_ty.walk_mut(f); self.projection_ty.walk_mut_binders(f, binders);
self.ty.walk_mut(f); self.ty.walk_mut_binders(f, binders);
} }
} }
@ -188,6 +188,7 @@ pub(crate) fn trait_solve_query(
} }
let canonical = goal.to_chalk(db).cast(); let canonical = goal.to_chalk(db).cast();
// We currently don't deal with universes (I think / hope they're not yet // We currently don't deal with universes (I think / hope they're not yet
// relevant for our use cases?) // relevant for our use cases?)
let u_canonical = chalk_ir::UCanonical { canonical, universes: 1 }; let u_canonical = chalk_ir::UCanonical { canonical, universes: 1 };