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

Infer whether a closure implements Fn, FnMut, etc based on what actions it takes #21805

Merged
merged 10 commits into from
Feb 1, 2015

Conversation

nikomatsakis
Copy link
Contributor

Currently, we only infer the kind of a closure based on the expected type or explicit annotation. If neither applies, we currently report an error. This pull request changes that case to defer the decision until we are able to analyze the actions of the closure: closures which mutate their environment require FnMut, closures which move out of their environment require FnOnce.

This PR is not the end of the story:

  • It does not remove the explicit annotations nor disregard them. The latter is the logical next step to removing them (we'll need a snapshot before we can do anything anyhow). Disregarding explicit annotations might expose more bugs since right now all closures in libstd/rustc use explicit annotations or the expected type, so this inference never kicks in.
  • The interaction with instantiating type parameter fallbacks leaves something to be desired. This is mostly just saying that the algorithm from Finalize defaulted type parameters rfcs#213 needs to be implemented, which is a separate bug. There are some semi-subtle interactions though because not knowing whether a closure is Fn vs FnMut prevents us from resolving obligations like F : FnMut(...), which can in turn prevent unification of some type parameters, which might (in turn) lead to undesired fallback. We can improve this situation however -- even if we don't know whether (or just how) F : FnMut(..) holds or not for some closure type F, we can still perform unification since we do know the argument and return types. Once kind inference is done, we can complete the F : FnMut(..) analysis -- which might yield an error if (e.g.) the F moves out of its environment.

r? @nick29581

@rust-highfive
Copy link
Collaborator

r? @pcwalton

(rust_highfive has picked a reviewer for you, use r? to override)

@nikomatsakis nikomatsakis assigned nrc and unassigned pcwalton Jan 31, 2015
@nikomatsakis
Copy link
Contributor Author

cc @aturon

@nikomatsakis
Copy link
Contributor Author

Oh, a note to the eventual reviewer -- the commits are mostly independent, but there's a bit of back-and-forth because I realized my initial strategy was overly complex. In particular, I envisioned the "deferred resolutions" introduced in 9a85a17 as a more general thing, but I realized that because we don't have to worry about recursive closures for kind inference, we can simplify everything if we specialize the deferred resolutions to calls, which is done in f9e46a8.

pub const tag_closure: uint = 0x96;
pub const tag_closure_type: uint = 0x97;
pub const tag_closure_kind: uint = 0x98;
// GAP 0x94...0x98
Copy link
Member

Choose a reason for hiding this comment

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

Could we not have this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I follow. Not have a gap? Not comment on the gap? I'm not sure how to avoid the gap except for renumbering everything below, which seems kind of pointless? (Or is there something obvious I'm overlooking?)

Copy link
Member

Choose a reason for hiding this comment

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

Forget about it, I'll remove it (the gap) whenever I touch this code again.

@eddyb
Copy link
Member

eddyb commented Jan 31, 2015

I took the liberty of finding a few nits - feel free to r=me unless you want a second review.

Any reason why this PR doesn't disregard explicit kinds? Maybe error if inference and the explicit kind mismatches.

@nikomatsakis
Copy link
Contributor Author

Any reason why this PR doesn't disregard explicit kinds? Maybe error if inference and the explicit kind mismatches.

No, I just ran out of time and decided to post it as is. Maybe I'll try it locally and see what happens. But it doesn't make sense to error: I actually expect inference to get different results than the expectation on a fairly regular basis. For example, if the expected kind is FnMut, but the closure is |x| x*2, then the inference would yield Fn -- but that's perfectly legal.

@eddyb
Copy link
Member

eddyb commented Feb 1, 2015

I was actually talking about |&mut: x| x*2, where one could change to |&: x| x*2 so it still compiles at stage0.
I am thinking that errors would potentially catch some bugs before they're fully removed, like a corner case that causes some form of mutation to be ignored, for example.

@nikomatsakis
Copy link
Contributor Author

@eddyb as an experiment, I tried removing all inference of expected kinds beyond what's implemented in this PR. I immediately hit some (minor) bugs around move closures, which I have fixed. After that, everything bootstraps, but I get some failures in run-pass tests due to the problem I described in bullet 2 of the header: "We can improve this situation however -- even if we don't know whether (or just how) F : FnMut(..) holds or not for some closure type F, we can still perform unification since we do know the argument and return types. Once kind inference is done, we can complete the F : FnMut(..) analysis -- which might yield an error if (e.g.) the F moves out of its environment. "

@nikomatsakis
Copy link
Contributor Author

So I'm personally inclined to land it as is and patch those other problems later.

doing the final checking for closure calls until after trait inference
is performed. This isn't important now, but it's essential if we are to
delay inferring the closure kind.
upvar inference.  Upvar inference can cause some obligations to be
deferred, notably things like `F : Sized` where `F` is a closure type,
or `F : FnMut`. Adjust the ordering therefore so that we process all
traits and apply fallback, do upvar inference, and only then start
reporting errors for outstanding obligations.
generate the closure type and closure kind separately.
specialized to closures, and invoke them as soon as we know the
closure kind. I thought initially we would need a fixed-point
inference algorithm but it appears I was mistaken, so we can do this.
…e weren't properly adjusting the closure kind in that case.
@nikomatsakis
Copy link
Contributor Author

Rebased, cleaned up @eddyb's nits, and fixed the easy bugs that were exposed by relying entirely on closure inference, but not the harder ones.

@nikomatsakis
Copy link
Contributor Author

@eddyb want to look over those last two commits?

@nikomatsakis nikomatsakis assigned eddyb and unassigned nrc Feb 1, 2015
@nikomatsakis
Copy link
Contributor Author

reassigning to eddyb since he did a review already

@eddyb
Copy link
Member

eddyb commented Feb 1, 2015

@bors r+ 870aea2

@bors
Copy link
Contributor

bors commented Feb 1, 2015

⌛ Testing commit 870aea2 with merge 0ab8d5d...

bors added a commit that referenced this pull request Feb 1, 2015
…ddyb

Currently, we only infer the kind of a closure based on the expected type or explicit annotation. If neither applies, we currently report an error. This pull request changes that case to defer the decision until we are able to analyze the actions of the closure: closures which mutate their environment require `FnMut`, closures which move out of their environment require `FnOnce`.

This PR is not the end of the story:

- It does not remove the explicit annotations nor disregard them. The latter is the logical next step to removing them (we'll need a snapshot before we can do anything anyhow). Disregarding explicit annotations might expose more bugs since right now all closures in libstd/rustc use explicit annotations or the expected type, so this inference never kicks in.
- The interaction with instantiating type parameter fallbacks leaves something to be desired. This is mostly just saying that the algorithm from rust-lang/rfcs#213 needs to be implemented, which is a separate bug. There are some semi-subtle interactions though because not knowing whether a closure is `Fn` vs `FnMut` prevents us from resolving obligations like `F : FnMut(...)`, which can in turn prevent unification of some type parameters, which might (in turn) lead to undesired fallback. We can improve this situation however -- even if we don't know whether (or just how) `F : FnMut(..)` holds or not for some closure type `F`, we can still perform unification since we *do* know the argument and return types. Once kind inference is done, we can complete the `F : FnMut(..)` analysis -- which might yield an error if (e.g.) the `F` moves out of its environment. 

r? @nick29581
@bors bors merged commit 870aea2 into rust-lang:master Feb 1, 2015
bors added a commit that referenced this pull request Feb 5, 2015
This needs a snapshot that includes #21805 before it can be merged.

There are some places where type inference regressed after I removed the annotations (see `FIXME`s). cc @nikomatsakis.

r? @eddyb or anyone
(I'll remove the `FIXME`s before merging, as they are only intended to point out regressions)
@nikomatsakis nikomatsakis deleted the closure-inference-refactor-1 branch March 30, 2016 16:12
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.

6 participants