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

introduce region-clauses into the `ParamEnv`, use to replace the `body_id` #42341

Open
nikomatsakis opened this Issue May 31, 2017 · 3 comments

Comments

Projects
None yet
3 participants
@nikomatsakis
Copy link
Contributor

nikomatsakis commented May 31, 2017

When we are in a closure body, we often gain additional implied "outlives" relation based on the types of the closure arguments. Consider this:

foo(|x: &T| ...)

Here, the type of x will be &'0 T for some anonymous region '0. Within the function body, then, we can conclude that T: '0. We currently handle this through a variety of messy schemes. As part of chalkificaton, I would like to consolidate this into a cleaner, more uniform handling of environments and universes. This issue lays out an "evolving" plan for doing that.

Current handling

Currently, for every trait obligation that we have to prove (e.g., T: Debug), we have an ObligationCause -- this is primarily used for debugging, in that it specifies why we have to prove this thing. However, this type also carries a body_id identifying the closure it came from. When we wind up having to prove an outlives relationship like Foo: 'a, we record this in the fulfillment context (a kind of record of things we have yet to prove), and we track the body_id where the obligation was incurred. Then, after type-checking, in the region-checking phase, we walk down the AST and figure out -- for each closure body -- what outlives facts we know at that point in time. We then pull out the list of outlives obligations from the fulfillment cx for a given body-id and try to prove them, using those facts. This whole process is complex and fragile.

Roughly how I want it to work

We now have the ability to have every obligation have its own, distinct environment (we used to have just one ambient environment). This means that instead of having this ad-hoc body_id field, we can just extend the environment when we enter into a closure body. So, for example, when type-checking the body of the closure in the beginning, our environment would be extended with a T: 'a outlives clause. We can do this right away, just as soon as we start type-checking the closure. This environment is automatically propagated to sub-goals, so when we wind up with some outlives obligations that we have to prove, they will have T: 'a in their list of clauses (i.e., the facts that they can draw upon). This means that regionck doesn't have to do anything "special" -- rather, the input to regionck will change from being a flat list of obligations, to obligations that can carry more environmental information.

There is one complication here:

  • when we first start type-checking closures, we often have not yet inferred the types of the arguments yet. This is partly why the current scheme defers everything until regionck. But this is ok, we can still insert "outlives" obligations that contain inference variables. We'll give them their final values before we go and solve things.

Actual steps

OK, I ran out of time for this part. =)

@djzin

This comment has been minimized.

Copy link
Contributor

djzin commented Jun 2, 2017

Is it possible to fully desugar closures into structs that implement the Fn traits? Or is this extremely ambitious / impossible? Because it sounds like if simplifying logic / more uniform handling of these things is the goal, turning closures into "just another function" would be a profitable step in this direction. This is certainly how I think about closures... but AFAICT they are treated as much more of a "fundamental" in the compiler currently

So if you have a closure foo(|&y| x + y), this would desugar into (roughly)

struct Closure<T> {
    x: T,
}

impl<'a, T, U> FnOnce<(&'a U,)> for Closure<T>
    where T: Add<U>,
          U: Copy + 'a,
{
    type Output = <T as Add<U>>::Output;
    extern "rust-call" fn call_once(self, (&y,): (&'a U,)) -> Self::Output {
        self.x + y
    }
}

foo(Closure{x: &x})
@nikomatsakis

This comment has been minimized.

Copy link
Contributor Author

nikomatsakis commented Jun 5, 2017

@djzin this does happen during MIR lowering, and eventually I do plan to move all the region processing so that it occurs on the MIR. It's good point -- maybe it's not worth worrying about the body_id, and instead a better idea to pursue the work to move regionck so that it occurs on MIR. That would also move us closer to non-lexical lifetimes.

@djzin

This comment has been minimized.

Copy link
Contributor

djzin commented Jun 11, 2017

So what does it take to move regionck to MIR? I am happy to help with this any way I can; I'm not super well-versed in this stuff at the moment but I can learn :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.