rust-lang / chalk

An implementation and definition of the Rust trait system using a PROLOG-like logic solver
https://rust-lang.github.io/chalk/book/
Other
1.81k stars 180 forks source link

experiment with converting semantic to syntactic equality #364

Open jackh726 opened 4 years ago

jackh726 commented 4 years ago

This issue has been assigned to @Areredify via this comment.

nikomatsakis commented 4 years ago

OK, it took me some time to find, but I finally found a link to the meeting where we discussed this topic in some depth. It was the meeting on 2019.10.16. So, to start, you can read those notes to get a high-level idea of what's being proposed.

I'll paste in the key idea here.

How it works today

Today, given an impl like this

impl<T: Iterator> Foo for T::Item { .. }

we would generate a clause like

forall<T: Iterator> {
    Implemented(<T as Iterator>::Item: Foo)
}

Then, when we do unification, we look for types like <T as Iterator>::Item, which represent unnormalized associated types -- we call those aliases. In unification, if we are ever asked to unify an alias A and some other type T, it always succeeds, and we generate a goal AliasEq(A = T):

https://github.com/rust-lang/chalk/blob/2bd2e039c95368634ba577a379b74be3f900a68d/chalk-solve/src/infer/unify.rs#L221-L237

So what happens is that we dynamically unfold the rule above. i.e., if we are matching against a goal like Implemented(u32: Foo), then we will match successfully and generate a subgoal AliasEq(<T as Iterator>::Item = u32) which has to be proven.

Why is this ungreat?

There are a few problems with this setup. First, as a general rule, I'd like the "solving logic" phase to be as simple as possible -- and our current unification scheme, which doesn't just unify types but also injects goals, makes things more dynamic and less amenable to (say) compilation in the future. In other words, I want the solving phase to be very close to prolog, and right now it's not.

But second, I want to create the invariant that whenever we ask for "all the impls of a trait", we have some idea what the "self type" is. This is helpful to things like rust-analyzer and rustc, since they can index impls by self-type and retrieve them more efficiently. But when we are solving a goal like <T as Iterator>::Item: Foo, they have no idea what that self-type is. Under the scheme I am contemplating, we would first be trying to normalize <T as Iterator>::Item first.

There's a third reason, but I'm not sure how much it applies given the work on the recursive solver. Still, one of the things I was exploring was a different model for region solving, and for that to work, I had to be able to separate out cases where I wanted to first regions that were syntactically equal (so 'a = 'a and nothing else) from semantically equal. In particular, it's possible to have 'a and 'b where 'a: 'b and 'b: 'a. Those two regions are semantically equivalent but syntactically distinct, and our current setup of unification -- which is based on semantic equivalence -- can't handle that.

How I would like it to work

I mentioned that today we unfold the goals dynamically as we unify things. There are two parts to the work here. The first part is that for an impl like the previous example:

impl<T: Iterator> Foo for T::Item { .. }

we would generate a clause like:

forall<T: Iterator, U> {
    Implemented(U: Foo) :- AliasEq(<T as Iterator>::Item = U)
}

The second part is that we would have to unfold goals as well. So something like

Implemented(<T as Iterator>::Item: Foo)

would become

exists<X> {
    AliasEq(<T as Iterator>::Item = X),
    Implemented(X: Foo)
}

In fact, I wrote up the transformation in some detail in a branch. I'll copy and paste that comment below for posterity.

nikomatsakis commented 4 years ago

Here is the comment from my branch, just in case that file gets lost.

The "syntactic equality" lowering converts Rust semantic type equality into syntactic type equality.

Syntactic vs semantic equality

So, what is the difference between syntactic vs semantic equality? Glad you asked!

nikomatsakis commented 4 years ago

A few things have changed since I pursued my older syntactic-equality branch:

I know I had another branch where I actually did complete the "semantic to syntactic" transformation, but only for clauses, and it didn't quite work. I might try to go find it, but perhaps the syntactic-equality branch is enough.

basil-cow commented 4 years ago

@rustbot claim