Skip to content
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

Count binders, not variables #360

Merged
merged 10 commits into from
Mar 28, 2020
Merged

Conversation

nikomatsakis
Copy link
Contributor

This branch modifies our notion of "bound variable" so that we count binders and not variables. This aligns more closely with how rustc does things.

Side note that along the way, I encountered at least one semi-serious (and pre-existing) problem with the setup of the implied bounds. I left a FIXME but we should open a follow-up issue on the topic.

Side note that I would dearly love to refactor the wf/coherence code that creates goals to introduce a GoalBuilder, somewhat analogous to the ClauseBuilder. The existing logic requires some very "careful coding" in that it makes extensive use of "copying and pasting" where-clauses and types from one context to another -- you can do this is you are very careful with how you setup your binders, but it's fragile and confusing.

If we have `where for<'a> T: Foo<'a>`, that should generate
`for<'a> FromEnv(T: Foo<'a>)` in the environment.
If we are counting binding levels, then this is important to keep the
debruijn indices from getting out of whack. In other words, `for ^1.0`
is very different from `^1.0`.
Copy link
Member

@jackh726 jackh726 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, overall I like the changes I think. What is the point of having an empty set of binders? rustc does this and it confuses the heck out of me. It seems like all it would do would add an extra step in solving? Do we need it to be correct here?

chalk-ir/src/debug.rs Outdated Show resolved Hide resolved
chalk-ir/src/fold/shift.rs Show resolved Hide resolved
chalk-ir/src/fold/binder_impls.rs Outdated Show resolved Hide resolved
chalk-ir/src/fold/subst.rs Show resolved Hide resolved
chalk-ir/src/lib.rs Outdated Show resolved Hide resolved
chalk-ir/src/lib.rs Outdated Show resolved Hide resolved
chalk-ir/src/lib.rs Outdated Show resolved Hide resolved
chalk-ir/src/lib.rs Outdated Show resolved Hide resolved
chalk-ir/src/lib.rs Outdated Show resolved Hide resolved
chalk-ir/src/lib.rs Outdated Show resolved Hide resolved
The new convention is:

* `shift_in` and `shifted_in` assume one level
* `shifted_in_by(N)` to shift by N levels

`shift_out` is analogous but it also always
returns `Option` instead of having an
assert.
This is more strongly typed. In the process, rename `shifted_in_by/shifted_out_by` to

* `shifted_in_from(D)` -- i.e., we are shifting a value from the binder D into this one
* `shifted_out_to(D)` -- i.e., we are shifting a value from this binder into the binder D

I at least find those names give me a better intuition for what's going on in
most cases. i.e., we are taking a term that was valid from some outer binder D
and shifting it *in* to the inner binder.
@nikomatsakis nikomatsakis mentioned this pull request Mar 27, 2020
@nikomatsakis
Copy link
Contributor Author

So, overall I like the changes I think. What is the point of having an empty set of binders? rustc does this and it confuses the heck out of me. It seems like all it would do would add an extra step in solving? Do we need it to be correct here?

To some extent it falls out from the approach of counting binders -- it would be possible to remove the binder, but it takes more work. The way the code is now, you never consider at all how many variables appear in a binder, which makes things a bit simpler: you're generally shifting by constant amounts (i.e., by one binding level) and not some variable number of locations. This also tends to be a bit less error-prone: this is why I uncovered a few oversights where we weren't adjusting indices. Since those binders tended to be empty, in the older system those oversights slipped through without being noticed.

To see what I mean, consider the term forall<X> { exists<> { X } }. Under the older approach, we would name X as ^0, because it's the innermost variable. This means that you can just remove the exists<> { .. } without changing anything about its contents. i.e., these two terms are equivalent:

forall<X> { exists<> { ^0 } }
forall<X> { ^0 }

In contrast, the exists<> binder could be removed and ^0 would retain the same meaning.

But under the new system, we would represent X as ^1.0. If you remove the exists<>, that now changes the meaning: in forall<X> { ^1.0 }, the ^1.0 refers to whatever binder (if any) encloses the forall. Of course, we could remove the exists and shift the counts inside, like forall<X> { ^0.0 }, but that would require more folding, and generally make the code a bit more complex.

If we find that there is some sort of efficiency cost, we could consider doing it, although I think there shouldn't really be any cost. We could probably (when solving goals) just notice the "irrelevant" binder and skip over it without doing any folding (but instead just incrementing some counter).

@jackh726
Copy link
Member

But under the new system, we would represent X as ^1.0. If you remove the exists<>, that now changes the meaning: in forall<X> { ^1.0 }, the ^1.0 refers to whatever binder (if any) encloses the forall. Of course, we could remove the exists and shift the counts inside, like forall<X> { ^0.0 }, but that would require more folding, and generally make the code a bit more complex.

This explanation helped me a lot, thanks.

These changes LGTM with the minor nits and stuff. Name change comments and such are up to your discretion.

@nikomatsakis nikomatsakis merged commit 61e5a0b into rust-lang:master Mar 28, 2020
flodiebold added a commit to flodiebold/rust-analyzer that referenced this pull request Apr 5, 2020
The big change here is counting binders, not
variables (rust-lang/chalk#360). We have to adapt to the
same scheme for our `Ty::Bound`. It's mostly fine though, even makes some things
more clear.
flodiebold added a commit to flodiebold/rust-analyzer that referenced this pull request Apr 5, 2020
The big change here is counting binders, not
variables (rust-lang/chalk#360). We have to adapt to the
same scheme for our `Ty::Bound`. It's mostly fine though, even makes some things
more clear.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants