-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fallback clauses considered harmful / how to handle projection equality #74
Comments
I am reminded that what I have really wanted to do for some time is to handle projection equality during lowering. Specifically, I'd like to lower an impl like this:
to a clause like this:
right now, we detect this "dynamically" by rewriting equalities between projections as we unify. I'd prefer for unification to be simple. This keeps the solvers simple. The main reason I've not pursued this transformation is that it will require us to deal with higher-ranked types and their instantiation. For example, how do we deal with One side effect though of the current setup is that the SLG solver can't handle unifications of the form |
OK, I fixed the problem with the SLG solver. Instead of unifying the "pending goal" with the result from a query, we now take the values directly from the answer substitute and substitute them into the pending goal. This is more efficient to boot. |
I also fixed the problem with the recursive solver by changing how we elaborate, which is kind of hacky but then again implied bounds need some care anyhow. |
In working on #73, I encountered a problem with the concept of a "fallback clause", which is currently a key part of how we handle normalization. The idea of a fallback clause is that it is a clause that we use if no other clauses apply. That seems fine but it's actually too weak: we wind up coming up with "unique" solutions that are not in fact unique.
Consider this example:
and this goal:
Here, there are two values for
U
andV
:exists<W> { U = Vec<W>, V = W }
U = T, V = T::Item
However, our current system will select the first one and be satisfied. This is because the second one is considered a "fallback" option, and hence since the first one is satisfied, it never gets considered. This is not true of the SLG solver, since I never could figure out how to integrate fallback into that solver -- for good reasons, I think.
I have a branch de-fallback that adresses this problem by removing the notion of fallback clauses. Instead, we have a new domain goal, "projection equality", which replaces normalization in a way (though normalization, as we will see, plays a role). When we attempt to unify two types T and U where at least one of those types is a projection, we "rewrite" to projection equality (really, we could rewrite all of unification into clauses, but I chose the more limited path here, since otherwise we'd have to handle higher-ranked types and substitution in the logic code).
Projection equality is defined by two clauses per associated item, which are defined when lowering the trait (well, when lowering the declaration of the associated item found in the trait). The first clause is what we used to call the "fallback" rule, basically covering the "skolemized" case:
The second clause uses a revised concept of normalization. Normalization in this setup is limited to applying an impl to rewrite a projection to the type found in the impl (whereas before it included the fallback case):
Both of these rules are created when lowering the trait. When lowering the impl, we would make a rule like:
This all seems to work pretty well. Note that
ProjectionEq
can never "guess" the right hand side unless normalization is impossible: that isexists<X> { ProjectionEq(<Vec<i32> as Iterator>::Item = X) }
is still ambiguous. But if you want to force normalize, you can use theNormalizes
relation (which would only be defined, in that example, wenX = i32
).However, the tests are currently failing because we are running into problems with the implied bounds elaborations and the limitations of the recursive solver. (The SLG solver handles it fine.) In particular, there is a rule that looks something like this:
This is basically saying, if we know (somehow) that
T::Item
is equal toU
, then we know thatT
must implementIterator
. It's reasonable, but it winds up causing us a problem. This is because, in the recursive solver, when we are doing normalization, we apply the normalization clause cited above, and then must prove that(Vec<T>: Iterator)
. To do that, we turn to our full set of clauses, which includes a clause from the impl (which is the right one) and the clause above. The reverse elaboration clause always yields ambiguous -- this is because there is no unique answer toProjectionEq
, andU
is unconstrained.I'm not 100% sure how to resolve this problem right now, so I thought I'd open the issue for a bit of discussion.
cc @scalexm @aturon
The text was updated successfully, but these errors were encountered: