Auto merge of #22126 - steveklabnik:gh21281, r=nikomatsakis

This is super black magic internals at the moment, but having it
somewhere semi-public seems good. The current versions weren't being
rendered, and they'll be useful for some people.

Fixes #21281

r? @nikomatsakis @kmcallister
This commit is contained in:
bors 2015-02-15 07:53:07 +00:00
commit b6d91a2bda
21 changed files with 2545 additions and 2639 deletions

View File

@ -0,0 +1,237 @@
# Type inference engine
This is loosely based on standard HM-type inference, but with an
extension to try and accommodate subtyping. There is nothing
principled about this extension; it's sound---I hope!---but it's a
heuristic, ultimately, and does not guarantee that it finds a valid
typing even if one exists (in fact, there are known scenarios where it
fails, some of which may eventually become problematic).
## Key idea
The main change is that each type variable T is associated with a
lower-bound L and an upper-bound U. L and U begin as bottom and top,
respectively, but gradually narrow in response to new constraints
being introduced. When a variable is finally resolved to a concrete
type, it can (theoretically) select any type that is a supertype of L
and a subtype of U.
There are several critical invariants which we maintain:
- the upper-bound of a variable only becomes lower and the lower-bound
only becomes higher over time;
- the lower-bound L is always a subtype of the upper bound U;
- the lower-bound L and upper-bound U never refer to other type variables,
but only to types (though those types may contain type variables).
> An aside: if the terms upper- and lower-bound confuse you, think of
> "supertype" and "subtype". The upper-bound is a "supertype"
> (super=upper in Latin, or something like that anyway) and the lower-bound
> is a "subtype" (sub=lower in Latin). I find it helps to visualize
> a simple class hierarchy, like Java minus interfaces and
> primitive types. The class Object is at the root (top) and other
> types lie in between. The bottom type is then the Null type.
> So the tree looks like:
>
> ```text
> Object
> / \
> String Other
> \ /
> (null)
> ```
>
> So the upper bound type is the "supertype" and the lower bound is the
> "subtype" (also, super and sub mean upper and lower in Latin, or something
> like that anyway).
## Satisfying constraints
At a primitive level, there is only one form of constraint that the
inference understands: a subtype relation. So the outside world can
say "make type A a subtype of type B". If there are variables
involved, the inferencer will adjust their upper- and lower-bounds as
needed to ensure that this relation is satisfied. (We also allow "make
type A equal to type B", but this is translated into "A <: B" and "B
<: A")
As stated above, we always maintain the invariant that type bounds
never refer to other variables. This keeps the inference relatively
simple, avoiding the scenario of having a kind of graph where we have
to pump constraints along and reach a fixed point, but it does impose
some heuristics in the case where the user is relating two type
variables A <: B.
Combining two variables such that variable A will forever be a subtype
of variable B is the trickiest part of the algorithm because there is
often no right choice---that is, the right choice will depend on
future constraints which we do not yet know. The problem comes about
because both A and B have bounds that can be adjusted in the future.
Let's look at some of the cases that can come up.
Imagine, to start, the best case, where both A and B have an upper and
lower bound (that is, the bounds are not top nor bot respectively). In
that case, if we're lucky, A.ub <: B.lb, and so we know that whatever
A and B should become, they will forever have the desired subtyping
relation. We can just leave things as they are.
### Option 1: Unify
However, suppose that A.ub is *not* a subtype of B.lb. In
that case, we must make a decision. One option is to unify A
and B so that they are one variable whose bounds are:
UB = GLB(A.ub, B.ub)
LB = LUB(A.lb, B.lb)
(Note that we will have to verify that LB <: UB; if it does not, the
types are not intersecting and there is an error) In that case, A <: B
holds trivially because A==B. However, we have now lost some
flexibility, because perhaps the user intended for A and B to end up
as different types and not the same type.
Pictorally, what this does is to take two distinct variables with
(hopefully not completely) distinct type ranges and produce one with
the intersection.
```text
B.ub B.ub
/\ /
A.ub / \ A.ub /
/ \ / \ \ /
/ X \ UB
/ / \ \ / \
/ / / \ / /
\ \ / / \ /
\ X / LB
\ / \ / / \
\ / \ / / \
A.lb B.lb A.lb B.lb
```
### Option 2: Relate UB/LB
Another option is to keep A and B as distinct variables but set their
bounds in such a way that, whatever happens, we know that A <: B will hold.
This can be achieved by ensuring that A.ub <: B.lb. In practice there
are two ways to do that, depicted pictorially here:
```text
Before Option #1 Option #2
B.ub B.ub B.ub
/\ / \ / \
A.ub / \ A.ub /(B')\ A.ub /(B')\
/ \ / \ \ / / \ / /
/ X \ __UB____/ UB /
/ / \ \ / | | /
/ / / \ / | | /
\ \ / / /(A')| | /
\ X / / LB ______LB/
\ / \ / / / \ / (A')/ \
\ / \ / \ / \ \ / \
A.lb B.lb A.lb B.lb A.lb B.lb
```
In these diagrams, UB and LB are defined as before. As you can see,
the new ranges `A'` and `B'` are quite different from the range that
would be produced by unifying the variables.
### What we do now
Our current technique is to *try* (transactionally) to relate the
existing bounds of A and B, if there are any (i.e., if `UB(A) != top
&& LB(B) != bot`). If that succeeds, we're done. If it fails, then
we merge A and B into same variable.
This is not clearly the correct course. For example, if `UB(A) !=
top` but `LB(B) == bot`, we could conceivably set `LB(B)` to `UB(A)`
and leave the variables unmerged. This is sometimes the better
course, it depends on the program.
The main case which fails today that I would like to support is:
```text
fn foo<T>(x: T, y: T) { ... }
fn bar() {
let x: @mut int = @mut 3;
let y: @int = @3;
foo(x, y);
}
```
In principle, the inferencer ought to find that the parameter `T` to
`foo(x, y)` is `@const int`. Today, however, it does not; this is
because the type variable `T` is merged with the type variable for
`X`, and thus inherits its UB/LB of `@mut int`. This leaves no
flexibility for `T` to later adjust to accommodate `@int`.
### What to do when not all bounds are present
In the prior discussion we assumed that A.ub was not top and B.lb was
not bot. Unfortunately this is rarely the case. Often type variables
have "lopsided" bounds. For example, if a variable in the program has
been initialized but has not been used, then its corresponding type
variable will have a lower bound but no upper bound. When that
variable is then used, we would like to know its upper bound---but we
don't have one! In this case we'll do different things depending on
how the variable is being used.
## Transactional support
Whenever we adjust merge variables or adjust their bounds, we always
keep a record of the old value. This allows the changes to be undone.
## Regions
I've only talked about type variables here, but region variables
follow the same principle. They have upper- and lower-bounds. A
region A is a subregion of a region B if A being valid implies that B
is valid. This basically corresponds to the block nesting structure:
the regions for outer block scopes are superregions of those for inner
block scopes.
## Integral and floating-point type variables
There is a third variety of type variable that we use only for
inferring the types of unsuffixed integer literals. Integral type
variables differ from general-purpose type variables in that there's
no subtyping relationship among the various integral types, so instead
of associating each variable with an upper and lower bound, we just
use simple unification. Each integer variable is associated with at
most one integer type. Floating point types are handled similarly to
integral types.
## GLB/LUB
Computing the greatest-lower-bound and least-upper-bound of two
types/regions is generally straightforward except when type variables
are involved. In that case, we follow a similar "try to use the bounds
when possible but otherwise merge the variables" strategy. In other
words, `GLB(A, B)` where `A` and `B` are variables will often result
in `A` and `B` being merged and the result being `A`.
## Type coercion
We have a notion of assignability which differs somewhat from
subtyping; in particular it may cause region borrowing to occur. See
the big comment later in this file on Type Coercion for specifics.
### In conclusion
I showed you three ways to relate `A` and `B`. There are also more,
of course, though I'm not sure if there are any more sensible options.
The main point is that there are various options, each of which
produce a distinct range of types for `A` and `B`. Depending on what
the correct values for A and B are, one of these options will be the
right choice: but of course we don't know the right values for A and B
yet, that's what we're trying to find! In our code, we opt to unify
(Option #1).
# Implementation details
We make use of a trait-like implementation strategy to consolidate
duplicated code between subtypes, GLB, and LUB computations. See the
section on "Type Combining" below for details.

View File

@ -1,247 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! # Type inference engine
//!
//! This is loosely based on standard HM-type inference, but with an
//! extension to try and accommodate subtyping. There is nothing
//! principled about this extension; it's sound---I hope!---but it's a
//! heuristic, ultimately, and does not guarantee that it finds a valid
//! typing even if one exists (in fact, there are known scenarios where it
//! fails, some of which may eventually become problematic).
//!
//! ## Key idea
//!
//! The main change is that each type variable T is associated with a
//! lower-bound L and an upper-bound U. L and U begin as bottom and top,
//! respectively, but gradually narrow in response to new constraints
//! being introduced. When a variable is finally resolved to a concrete
//! type, it can (theoretically) select any type that is a supertype of L
//! and a subtype of U.
//!
//! There are several critical invariants which we maintain:
//!
//! - the upper-bound of a variable only becomes lower and the lower-bound
//! only becomes higher over time;
//! - the lower-bound L is always a subtype of the upper bound U;
//! - the lower-bound L and upper-bound U never refer to other type variables,
//! but only to types (though those types may contain type variables).
//!
//! > An aside: if the terms upper- and lower-bound confuse you, think of
//! > "supertype" and "subtype". The upper-bound is a "supertype"
//! > (super=upper in Latin, or something like that anyway) and the lower-bound
//! > is a "subtype" (sub=lower in Latin). I find it helps to visualize
//! > a simple class hierarchy, like Java minus interfaces and
//! > primitive types. The class Object is at the root (top) and other
//! > types lie in between. The bottom type is then the Null type.
//! > So the tree looks like:
//! >
//! > ```text
//! > Object
//! > / \
//! > String Other
//! > \ /
//! > (null)
//! > ```
//! >
//! > So the upper bound type is the "supertype" and the lower bound is the
//! > "subtype" (also, super and sub mean upper and lower in Latin, or something
//! > like that anyway).
//!
//! ## Satisfying constraints
//!
//! At a primitive level, there is only one form of constraint that the
//! inference understands: a subtype relation. So the outside world can
//! say "make type A a subtype of type B". If there are variables
//! involved, the inferencer will adjust their upper- and lower-bounds as
//! needed to ensure that this relation is satisfied. (We also allow "make
//! type A equal to type B", but this is translated into "A <: B" and "B
//! <: A")
//!
//! As stated above, we always maintain the invariant that type bounds
//! never refer to other variables. This keeps the inference relatively
//! simple, avoiding the scenario of having a kind of graph where we have
//! to pump constraints along and reach a fixed point, but it does impose
//! some heuristics in the case where the user is relating two type
//! variables A <: B.
//!
//! Combining two variables such that variable A will forever be a subtype
//! of variable B is the trickiest part of the algorithm because there is
//! often no right choice---that is, the right choice will depend on
//! future constraints which we do not yet know. The problem comes about
//! because both A and B have bounds that can be adjusted in the future.
//! Let's look at some of the cases that can come up.
//!
//! Imagine, to start, the best case, where both A and B have an upper and
//! lower bound (that is, the bounds are not top nor bot respectively). In
//! that case, if we're lucky, A.ub <: B.lb, and so we know that whatever
//! A and B should become, they will forever have the desired subtyping
//! relation. We can just leave things as they are.
//!
//! ### Option 1: Unify
//!
//! However, suppose that A.ub is *not* a subtype of B.lb. In
//! that case, we must make a decision. One option is to unify A
//! and B so that they are one variable whose bounds are:
//!
//! UB = GLB(A.ub, B.ub)
//! LB = LUB(A.lb, B.lb)
//!
//! (Note that we will have to verify that LB <: UB; if it does not, the
//! types are not intersecting and there is an error) In that case, A <: B
//! holds trivially because A==B. However, we have now lost some
//! flexibility, because perhaps the user intended for A and B to end up
//! as different types and not the same type.
//!
//! Pictorally, what this does is to take two distinct variables with
//! (hopefully not completely) distinct type ranges and produce one with
//! the intersection.
//!
//! ```text
//! B.ub B.ub
//! /\ /
//! A.ub / \ A.ub /
//! / \ / \ \ /
//! / X \ UB
//! / / \ \ / \
//! / / / \ / /
//! \ \ / / \ /
//! \ X / LB
//! \ / \ / / \
//! \ / \ / / \
//! A.lb B.lb A.lb B.lb
//! ```
//!
//!
//! ### Option 2: Relate UB/LB
//!
//! Another option is to keep A and B as distinct variables but set their
//! bounds in such a way that, whatever happens, we know that A <: B will hold.
//! This can be achieved by ensuring that A.ub <: B.lb. In practice there
//! are two ways to do that, depicted pictorially here:
//!
//! ```text
//! Before Option #1 Option #2
//!
//! B.ub B.ub B.ub
//! /\ / \ / \
//! A.ub / \ A.ub /(B')\ A.ub /(B')\
//! / \ / \ \ / / \ / /
//! / X \ __UB____/ UB /
//! / / \ \ / | | /
//! / / / \ / | | /
//! \ \ / / /(A')| | /
//! \ X / / LB ______LB/
//! \ / \ / / / \ / (A')/ \
//! \ / \ / \ / \ \ / \
//! A.lb B.lb A.lb B.lb A.lb B.lb
//! ```
//!
//! In these diagrams, UB and LB are defined as before. As you can see,
//! the new ranges `A'` and `B'` are quite different from the range that
//! would be produced by unifying the variables.
//!
//! ### What we do now
//!
//! Our current technique is to *try* (transactionally) to relate the
//! existing bounds of A and B, if there are any (i.e., if `UB(A) != top
//! && LB(B) != bot`). If that succeeds, we're done. If it fails, then
//! we merge A and B into same variable.
//!
//! This is not clearly the correct course. For example, if `UB(A) !=
//! top` but `LB(B) == bot`, we could conceivably set `LB(B)` to `UB(A)`
//! and leave the variables unmerged. This is sometimes the better
//! course, it depends on the program.
//!
//! The main case which fails today that I would like to support is:
//!
//! ```text
//! fn foo<T>(x: T, y: T) { ... }
//!
//! fn bar() {
//! let x: @mut int = @mut 3;
//! let y: @int = @3;
//! foo(x, y);
//! }
//! ```
//!
//! In principle, the inferencer ought to find that the parameter `T` to
//! `foo(x, y)` is `@const int`. Today, however, it does not; this is
//! because the type variable `T` is merged with the type variable for
//! `X`, and thus inherits its UB/LB of `@mut int`. This leaves no
//! flexibility for `T` to later adjust to accommodate `@int`.
//!
//! ### What to do when not all bounds are present
//!
//! In the prior discussion we assumed that A.ub was not top and B.lb was
//! not bot. Unfortunately this is rarely the case. Often type variables
//! have "lopsided" bounds. For example, if a variable in the program has
//! been initialized but has not been used, then its corresponding type
//! variable will have a lower bound but no upper bound. When that
//! variable is then used, we would like to know its upper bound---but we
//! don't have one! In this case we'll do different things depending on
//! how the variable is being used.
//!
//! ## Transactional support
//!
//! Whenever we adjust merge variables or adjust their bounds, we always
//! keep a record of the old value. This allows the changes to be undone.
//!
//! ## Regions
//!
//! I've only talked about type variables here, but region variables
//! follow the same principle. They have upper- and lower-bounds. A
//! region A is a subregion of a region B if A being valid implies that B
//! is valid. This basically corresponds to the block nesting structure:
//! the regions for outer block scopes are superregions of those for inner
//! block scopes.
//!
//! ## Integral and floating-point type variables
//!
//! There is a third variety of type variable that we use only for
//! inferring the types of unsuffixed integer literals. Integral type
//! variables differ from general-purpose type variables in that there's
//! no subtyping relationship among the various integral types, so instead
//! of associating each variable with an upper and lower bound, we just
//! use simple unification. Each integer variable is associated with at
//! most one integer type. Floating point types are handled similarly to
//! integral types.
//!
//! ## GLB/LUB
//!
//! Computing the greatest-lower-bound and least-upper-bound of two
//! types/regions is generally straightforward except when type variables
//! are involved. In that case, we follow a similar "try to use the bounds
//! when possible but otherwise merge the variables" strategy. In other
//! words, `GLB(A, B)` where `A` and `B` are variables will often result
//! in `A` and `B` being merged and the result being `A`.
//!
//! ## Type coercion
//!
//! We have a notion of assignability which differs somewhat from
//! subtyping; in particular it may cause region borrowing to occur. See
//! the big comment later in this file on Type Coercion for specifics.
//!
//! ### In conclusion
//!
//! I showed you three ways to relate `A` and `B`. There are also more,
//! of course, though I'm not sure if there are any more sensible options.
//! The main point is that there are various options, each of which
//! produce a distinct range of types for `A` and `B`. Depending on what
//! the correct values for A and B are, one of these options will be the
//! right choice: but of course we don't know the right values for A and B
//! yet, that's what we're trying to find! In our code, we opt to unify
//! (Option #1).
//!
//! # Implementation details
//!
//! We make use of a trait-like implementation strategy to consolidate
//! duplicated code between subtypes, GLB, and LUB computations. See the
//! section on "Type Combining" below for details.

View File

@ -0,0 +1,403 @@
# Skolemization and functions
One of the trickiest and most subtle aspects of regions is dealing
with higher-ranked things which include bound region variables, such
as function types. I strongly suggest that if you want to understand
the situation, you read this paper (which is, admittedly, very long,
but you don't have to read the whole thing):
http://research.microsoft.com/en-us/um/people/simonpj/papers/higher-rank/
Although my explanation will never compete with SPJ's (for one thing,
his is approximately 100 pages), I will attempt to explain the basic
problem and also how we solve it. Note that the paper only discusses
subtyping, not the computation of LUB/GLB.
The problem we are addressing is that there is a kind of subtyping
between functions with bound region parameters. Consider, for
example, whether the following relation holds:
for<'a> fn(&'a int) <: for<'b> fn(&'b int)? (Yes, a => b)
The answer is that of course it does. These two types are basically
the same, except that in one we used the name `a` and one we used
the name `b`.
In the examples that follow, it becomes very important to know whether
a lifetime is bound in a function type (that is, is a lifetime
parameter) or appears free (is defined in some outer scope).
Therefore, from now on I will always write the bindings explicitly,
using the Rust syntax `for<'a> fn(&'a int)` to indicate that `a` is a
lifetime parameter.
Now let's consider two more function types. Here, we assume that the
`'b` lifetime is defined somewhere outside and hence is not a lifetime
parameter bound by the function type (it "appears free"):
for<'a> fn(&'a int) <: fn(&'b int)? (Yes, a => b)
This subtyping relation does in fact hold. To see why, you have to
consider what subtyping means. One way to look at `T1 <: T2` is to
say that it means that it is always ok to treat an instance of `T1` as
if it had the type `T2`. So, with our functions, it is always ok to
treat a function that can take pointers with any lifetime as if it
were a function that can only take a pointer with the specific
lifetime `'b`. After all, `'b` is a lifetime, after all, and
the function can take values of any lifetime.
You can also look at subtyping as the *is a* relationship. This amounts
to the same thing: a function that accepts pointers with any lifetime
*is a* function that accepts pointers with some specific lifetime.
So, what if we reverse the order of the two function types, like this:
fn(&'b int) <: for<'a> fn(&'a int)? (No)
Does the subtyping relationship still hold? The answer of course is
no. In this case, the function accepts *only the lifetime `'b`*,
so it is not reasonable to treat it as if it were a function that
accepted any lifetime.
What about these two examples:
for<'a,'b> fn(&'a int, &'b int) <: for<'a> fn(&'a int, &'a int)? (Yes)
for<'a> fn(&'a int, &'a int) <: for<'a,'b> fn(&'a int, &'b int)? (No)
Here, it is true that functions which take two pointers with any two
lifetimes can be treated as if they only accepted two pointers with
the same lifetime, but not the reverse.
## The algorithm
Here is the algorithm we use to perform the subtyping check:
1. Replace all bound regions in the subtype with new variables
2. Replace all bound regions in the supertype with skolemized
equivalents. A "skolemized" region is just a new fresh region
name.
3. Check that the parameter and return types match as normal
4. Ensure that no skolemized regions 'leak' into region variables
visible from "the outside"
Let's walk through some examples and see how this algorithm plays out.
#### First example
We'll start with the first example, which was:
1. for<'a> fn(&'a T) <: for<'b> fn(&'b T)? Yes: a -> b
After steps 1 and 2 of the algorithm we will have replaced the types
like so:
1. fn(&'A T) <: fn(&'x T)?
Here the upper case `&A` indicates a *region variable*, that is, a
region whose value is being inferred by the system. I also replaced
`&b` with `&x`---I'll use letters late in the alphabet (`x`, `y`, `z`)
to indicate skolemized region names. We can assume they don't appear
elsewhere. Note that neither the sub- nor the supertype bind any
region names anymore (as indicated by the absence of `<` and `>`).
The next step is to check that the parameter types match. Because
parameters are contravariant, this means that we check whether:
&'x T <: &'A T
Region pointers are contravariant so this implies that
&A <= &x
must hold, where `<=` is the subregion relationship. Processing
*this* constrain simply adds a constraint into our graph that `&A <=
&x` and is considered successful (it can, for example, be satisfied by
choosing the value `&x` for `&A`).
So far we have encountered no error, so the subtype check succeeds.
#### The third example
Now let's look first at the third example, which was:
3. fn(&'a T) <: for<'b> fn(&'b T)? No!
After steps 1 and 2 of the algorithm we will have replaced the types
like so:
3. fn(&'a T) <: fn(&'x T)?
This looks pretty much the same as before, except that on the LHS
`'a` was not bound, and hence was left as-is and not replaced with
a variable. The next step is again to check that the parameter types
match. This will ultimately require (as before) that `'a` <= `&x`
must hold: but this does not hold. `self` and `x` are both distinct
free regions. So the subtype check fails.
#### Checking for skolemization leaks
You may be wondering about that mysterious last step in the algorithm.
So far it has not been relevant. The purpose of that last step is to
catch something like *this*:
for<'a> fn() -> fn(&'a T) <: fn() -> for<'b> fn(&'b T)? No.
Here the function types are the same but for where the binding occurs.
The subtype returns a function that expects a value in precisely one
region. The supertype returns a function that expects a value in any
region. If we allow an instance of the subtype to be used where the
supertype is expected, then, someone could call the fn and think that
the return value has type `fn<b>(&'b T)` when it really has type
`fn(&'a T)` (this is case #3, above). Bad.
So let's step through what happens when we perform this subtype check.
We first replace the bound regions in the subtype (the supertype has
no bound regions). This gives us:
fn() -> fn(&'A T) <: fn() -> for<'b> fn(&'b T)?
Now we compare the return types, which are covariant, and hence we have:
fn(&'A T) <: for<'b> fn(&'b T)?
Here we skolemize the bound region in the supertype to yield:
fn(&'A T) <: fn(&'x T)?
And then proceed to compare the argument types:
&'x T <: &'A T
'A <= 'x
Finally, this is where it gets interesting! This is where an error
*should* be reported. But in fact this will not happen. The reason why
is that `A` is a variable: we will infer that its value is the fresh
region `x` and think that everything is happy. In fact, this behavior
is *necessary*, it was key to the first example we walked through.
The difference between this example and the first one is that the variable
`A` already existed at the point where the skolemization occurred. In
the first example, you had two functions:
for<'a> fn(&'a T) <: for<'b> fn(&'b T)
and hence `&A` and `&x` were created "together". In general, the
intention of the skolemized names is that they are supposed to be
fresh names that could never be equal to anything from the outside.
But when inference comes into play, we might not be respecting this
rule.
So the way we solve this is to add a fourth step that examines the
constraints that refer to skolemized names. Basically, consider a
non-directed version of the constraint graph. Let `Tainted(x)` be the
set of all things reachable from a skolemized variable `x`.
`Tainted(x)` should not contain any regions that existed before the
step at which the skolemization was performed. So this case here
would fail because `&x` was created alone, but is relatable to `&A`.
## Computing the LUB and GLB
The paper I pointed you at is written for Haskell. It does not
therefore considering subtyping and in particular does not consider
LUB or GLB computation. We have to consider this. Here is the
algorithm I implemented.
First though, let's discuss what we are trying to compute in more
detail. The LUB is basically the "common supertype" and the GLB is
"common subtype"; one catch is that the LUB should be the
*most-specific* common supertype and the GLB should be *most general*
common subtype (as opposed to any common supertype or any common
subtype).
Anyway, to help clarify, here is a table containing some function
pairs and their LUB/GLB (for conciseness, in this table, I'm just
including the lifetimes here, not the rest of the types, and I'm
writing `fn<>` instead of `for<> fn`):
```
Type 1 Type 2 LUB GLB
fn<'a>('a) fn('X) fn('X) fn<'a>('a)
fn('a) fn('X) -- fn<'a>('a)
fn<'a,'b>('a, 'b) fn<'x>('x, 'x) fn<'a>('a, 'a) fn<'a,'b>('a, 'b)
fn<'a,'b>('a, 'b, 'a) fn<'x,'y>('x, 'y, 'y) fn<'a>('a, 'a, 'a) fn<'a,'b,'c>('a,'b,'c)
```
### Conventions
I use lower-case letters (e.g., `&a`) for bound regions and upper-case
letters for free regions (`&A`). Region variables written with a
dollar-sign (e.g., `$a`). I will try to remember to enumerate the
bound-regions on the fn type as well (e.g., `for<'a> fn(&a)`).
### High-level summary
Both the LUB and the GLB algorithms work in a similar fashion. They
begin by replacing all bound regions (on both sides) with fresh region
inference variables. Therefore, both functions are converted to types
that contain only free regions. We can then compute the LUB/GLB in a
straightforward way, as described in `combine.rs`. This results in an
interim type T. The algorithms then examine the regions that appear
in T and try to, in some cases, replace them with bound regions to
yield the final result.
To decide whether to replace a region `R` that appears in `T` with
a bound region, the algorithms make use of two bits of
information. First is a set `V` that contains all region
variables created as part of the LUB/GLB computation (roughly; see
`region_vars_confined_to_snapshot()` for full details). `V` will
contain the region variables created to replace the bound regions
in the input types, but it also contains 'intermediate' variables
created to represent the LUB/GLB of individual regions.
Basically, when asked to compute the LUB/GLB of a region variable
with another region, the inferencer cannot oblige immediately
since the values of that variables are not known. Therefore, it
creates a new variable that is related to the two regions. For
example, the LUB of two variables `$x` and `$y` is a fresh
variable `$z` that is constrained such that `$x <= $z` and `$y <=
$z`. So `V` will contain these intermediate variables as well.
The other important factor in deciding how to replace a region in T is
the function `Tainted($r)` which, for a region variable, identifies
all regions that the region variable is related to in some way
(`Tainted()` made an appearance in the subtype computation as well).
### LUB
The LUB algorithm proceeds in three steps:
1. Replace all bound regions (on both sides) with fresh region
inference variables.
2. Compute the LUB "as normal", meaning compute the GLB of each
pair of argument types and the LUB of the return types and
so forth. Combine those to a new function type `F`.
3. Replace each region `R` that appears in `F` as follows:
- Let `V` be the set of variables created during the LUB
computational steps 1 and 2, as described in the previous section.
- If `R` is not in `V`, replace `R` with itself.
- If `Tainted(R)` contains a region that is not in `V`,
replace `R` with itself.
- Otherwise, select the earliest variable in `Tainted(R)` that originates
from the left-hand side and replace `R` with the bound region that
this variable was a replacement for.
So, let's work through the simplest example: `fn(&A)` and `for<'a> fn(&a)`.
In this case, `&a` will be replaced with `$a` and the interim LUB type
`fn($b)` will be computed, where `$b=GLB(&A,$a)`. Therefore, `V =
{$a, $b}` and `Tainted($b) = { $b, $a, &A }`. When we go to replace
`$b`, we find that since `&A \in Tainted($b)` is not a member of `V`,
we leave `$b` as is. When region inference happens, `$b` will be
resolved to `&A`, as we wanted.
Let's look at a more complex one: `fn(&a, &b)` and `fn(&x, &x)`. In
this case, we'll end up with a (pre-replacement) LUB type of `fn(&g,
&h)` and a graph that looks like:
```
$a $b *--$x
\ \ / /
\ $h-* /
$g-----------*
```
Here `$g` and `$h` are fresh variables that are created to represent
the LUB/GLB of things requiring inference. This means that `V` and
`Tainted` will look like:
```
V = {$a, $b, $g, $h, $x}
Tainted($g) = Tainted($h) = { $a, $b, $h, $g, $x }
```
Therefore we replace both `$g` and `$h` with `$a`, and end up
with the type `fn(&a, &a)`.
### GLB
The procedure for computing the GLB is similar. The difference lies
in computing the replacements for the various variables. For each
region `R` that appears in the type `F`, we again compute `Tainted(R)`
and examine the results:
1. If `R` is not in `V`, it is not replaced.
2. Else, if `Tainted(R)` contains only variables in `V`, and it
contains exactly one variable from the LHS and one variable from
the RHS, then `R` can be mapped to the bound version of the
variable from the LHS.
3. Else, if `Tainted(R)` contains no variable from the LHS and no
variable from the RHS, then `R` can be mapped to itself.
4. Else, `R` is mapped to a fresh bound variable.
These rules are pretty complex. Let's look at some examples to see
how they play out.
Out first example was `fn(&a)` and `fn(&X)`. In this case, `&a` will
be replaced with `$a` and we will ultimately compute a
(pre-replacement) GLB type of `fn($g)` where `$g=LUB($a,&X)`.
Therefore, `V={$a,$g}` and `Tainted($g)={$g,$a,&X}. To find the
replacement for `$g` we consult the rules above:
- Rule (1) does not apply because `$g \in V`
- Rule (2) does not apply because `&X \in Tainted($g)`
- Rule (3) does not apply because `$a \in Tainted($g)`
- Hence, by rule (4), we replace `$g` with a fresh bound variable `&z`.
So our final result is `fn(&z)`, which is correct.
The next example is `fn(&A)` and `fn(&Z)`. In this case, we will again
have a (pre-replacement) GLB of `fn(&g)`, where `$g = LUB(&A,&Z)`.
Therefore, `V={$g}` and `Tainted($g) = {$g, &A, &Z}`. In this case,
by rule (3), `$g` is mapped to itself, and hence the result is
`fn($g)`. This result is correct (in this case, at least), but it is
indicative of a case that *can* lead us into concluding that there is
no GLB when in fact a GLB does exist. See the section "Questionable
Results" below for more details.
The next example is `fn(&a, &b)` and `fn(&c, &c)`. In this case, as
before, we'll end up with `F=fn($g, $h)` where `Tainted($g) =
Tainted($h) = {$g, $h, $a, $b, $c}`. Only rule (4) applies and hence
we'll select fresh bound variables `y` and `z` and wind up with
`fn(&y, &z)`.
For the last example, let's consider what may seem trivial, but is
not: `fn(&a, &a)` and `fn(&b, &b)`. In this case, we'll get `F=fn($g,
$h)` where `Tainted($g) = {$g, $a, $x}` and `Tainted($h) = {$h, $a,
$x}`. Both of these sets contain exactly one bound variable from each
side, so we'll map them both to `&a`, resulting in `fn(&a, &a)`, which
is the desired result.
### Shortcomings and correctness
You may be wondering whether this algorithm is correct. The answer is
"sort of". There are definitely cases where they fail to compute a
result even though a correct result exists. I believe, though, that
if they succeed, then the result is valid, and I will attempt to
convince you. The basic argument is that the "pre-replacement" step
computes a set of constraints. The replacements, then, attempt to
satisfy those constraints, using bound identifiers where needed.
For now I will briefly go over the cases for LUB/GLB and identify
their intent:
- LUB:
- The region variables that are substituted in place of bound regions
are intended to collect constraints on those bound regions.
- If Tainted(R) contains only values in V, then this region is unconstrained
and can therefore be generalized, otherwise it cannot.
- GLB:
- The region variables that are substituted in place of bound regions
are intended to collect constraints on those bound regions.
- If Tainted(R) contains exactly one variable from each side, and
only variables in V, that indicates that those two bound regions
must be equated.
- Otherwise, if Tainted(R) references any variables from left or right
side, then it is trying to combine a bound region with a free one or
multiple bound regions, so we need to select fresh bound regions.
Sorry this is more of a shorthand to myself. I will try to write up something
more convincing in the future.
#### Where are the algorithms wrong?
- The pre-replacement computation can fail even though using a
bound-region would have succeeded.
- We will compute GLB(fn(fn($a)), fn(fn($b))) as fn($c) where $c is the
GLB of $a and $b. But if inference finds that $a and $b must be mapped
to regions without a GLB, then this is effectively a failure to compute
the GLB. However, the result `fn<$c>(fn($c))` is a valid GLB.

View File

@ -1,413 +0,0 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! # Skolemization and functions
//!
//! One of the trickiest and most subtle aspects of regions is dealing
//! with higher-ranked things which include bound region variables, such
//! as function types. I strongly suggest that if you want to understand
//! the situation, you read this paper (which is, admittedly, very long,
//! but you don't have to read the whole thing):
//!
//! http://research.microsoft.com/en-us/um/people/simonpj/papers/higher-rank/
//!
//! Although my explanation will never compete with SPJ's (for one thing,
//! his is approximately 100 pages), I will attempt to explain the basic
//! problem and also how we solve it. Note that the paper only discusses
//! subtyping, not the computation of LUB/GLB.
//!
//! The problem we are addressing is that there is a kind of subtyping
//! between functions with bound region parameters. Consider, for
//! example, whether the following relation holds:
//!
//! for<'a> fn(&'a int) <: for<'b> fn(&'b int)? (Yes, a => b)
//!
//! The answer is that of course it does. These two types are basically
//! the same, except that in one we used the name `a` and one we used
//! the name `b`.
//!
//! In the examples that follow, it becomes very important to know whether
//! a lifetime is bound in a function type (that is, is a lifetime
//! parameter) or appears free (is defined in some outer scope).
//! Therefore, from now on I will always write the bindings explicitly,
//! using the Rust syntax `for<'a> fn(&'a int)` to indicate that `a` is a
//! lifetime parameter.
//!
//! Now let's consider two more function types. Here, we assume that the
//! `'b` lifetime is defined somewhere outside and hence is not a lifetime
//! parameter bound by the function type (it "appears free"):
//!
//! for<'a> fn(&'a int) <: fn(&'b int)? (Yes, a => b)
//!
//! This subtyping relation does in fact hold. To see why, you have to
//! consider what subtyping means. One way to look at `T1 <: T2` is to
//! say that it means that it is always ok to treat an instance of `T1` as
//! if it had the type `T2`. So, with our functions, it is always ok to
//! treat a function that can take pointers with any lifetime as if it
//! were a function that can only take a pointer with the specific
//! lifetime `'b`. After all, `'b` is a lifetime, after all, and
//! the function can take values of any lifetime.
//!
//! You can also look at subtyping as the *is a* relationship. This amounts
//! to the same thing: a function that accepts pointers with any lifetime
//! *is a* function that accepts pointers with some specific lifetime.
//!
//! So, what if we reverse the order of the two function types, like this:
//!
//! fn(&'b int) <: for<'a> fn(&'a int)? (No)
//!
//! Does the subtyping relationship still hold? The answer of course is
//! no. In this case, the function accepts *only the lifetime `'b`*,
//! so it is not reasonable to treat it as if it were a function that
//! accepted any lifetime.
//!
//! What about these two examples:
//!
//! for<'a,'b> fn(&'a int, &'b int) <: for<'a> fn(&'a int, &'a int)? (Yes)
//! for<'a> fn(&'a int, &'a int) <: for<'a,'b> fn(&'a int, &'b int)? (No)
//!
//! Here, it is true that functions which take two pointers with any two
//! lifetimes can be treated as if they only accepted two pointers with
//! the same lifetime, but not the reverse.
//!
//! ## The algorithm
//!
//! Here is the algorithm we use to perform the subtyping check:
//!
//! 1. Replace all bound regions in the subtype with new variables
//! 2. Replace all bound regions in the supertype with skolemized
//! equivalents. A "skolemized" region is just a new fresh region
//! name.
//! 3. Check that the parameter and return types match as normal
//! 4. Ensure that no skolemized regions 'leak' into region variables
//! visible from "the outside"
//!
//! Let's walk through some examples and see how this algorithm plays out.
//!
//! #### First example
//!
//! We'll start with the first example, which was:
//!
//! 1. for<'a> fn(&'a T) <: for<'b> fn(&'b T)? Yes: a -> b
//!
//! After steps 1 and 2 of the algorithm we will have replaced the types
//! like so:
//!
//! 1. fn(&'A T) <: fn(&'x T)?
//!
//! Here the upper case `&A` indicates a *region variable*, that is, a
//! region whose value is being inferred by the system. I also replaced
//! `&b` with `&x`---I'll use letters late in the alphabet (`x`, `y`, `z`)
//! to indicate skolemized region names. We can assume they don't appear
//! elsewhere. Note that neither the sub- nor the supertype bind any
//! region names anymore (as indicated by the absence of `<` and `>`).
//!
//! The next step is to check that the parameter types match. Because
//! parameters are contravariant, this means that we check whether:
//!
//! &'x T <: &'A T
//!
//! Region pointers are contravariant so this implies that
//!
//! &A <= &x
//!
//! must hold, where `<=` is the subregion relationship. Processing
//! *this* constrain simply adds a constraint into our graph that `&A <=
//! &x` and is considered successful (it can, for example, be satisfied by
//! choosing the value `&x` for `&A`).
//!
//! So far we have encountered no error, so the subtype check succeeds.
//!
//! #### The third example
//!
//! Now let's look first at the third example, which was:
//!
//! 3. fn(&'a T) <: for<'b> fn(&'b T)? No!
//!
//! After steps 1 and 2 of the algorithm we will have replaced the types
//! like so:
//!
//! 3. fn(&'a T) <: fn(&'x T)?
//!
//! This looks pretty much the same as before, except that on the LHS
//! `'a` was not bound, and hence was left as-is and not replaced with
//! a variable. The next step is again to check that the parameter types
//! match. This will ultimately require (as before) that `'a` <= `&x`
//! must hold: but this does not hold. `self` and `x` are both distinct
//! free regions. So the subtype check fails.
//!
//! #### Checking for skolemization leaks
//!
//! You may be wondering about that mysterious last step in the algorithm.
//! So far it has not been relevant. The purpose of that last step is to
//! catch something like *this*:
//!
//! for<'a> fn() -> fn(&'a T) <: fn() -> for<'b> fn(&'b T)? No.
//!
//! Here the function types are the same but for where the binding occurs.
//! The subtype returns a function that expects a value in precisely one
//! region. The supertype returns a function that expects a value in any
//! region. If we allow an instance of the subtype to be used where the
//! supertype is expected, then, someone could call the fn and think that
//! the return value has type `fn<b>(&'b T)` when it really has type
//! `fn(&'a T)` (this is case #3, above). Bad.
//!
//! So let's step through what happens when we perform this subtype check.
//! We first replace the bound regions in the subtype (the supertype has
//! no bound regions). This gives us:
//!
//! fn() -> fn(&'A T) <: fn() -> for<'b> fn(&'b T)?
//!
//! Now we compare the return types, which are covariant, and hence we have:
//!
//! fn(&'A T) <: for<'b> fn(&'b T)?
//!
//! Here we skolemize the bound region in the supertype to yield:
//!
//! fn(&'A T) <: fn(&'x T)?
//!
//! And then proceed to compare the argument types:
//!
//! &'x T <: &'A T
//! 'A <= 'x
//!
//! Finally, this is where it gets interesting! This is where an error
//! *should* be reported. But in fact this will not happen. The reason why
//! is that `A` is a variable: we will infer that its value is the fresh
//! region `x` and think that everything is happy. In fact, this behavior
//! is *necessary*, it was key to the first example we walked through.
//!
//! The difference between this example and the first one is that the variable
//! `A` already existed at the point where the skolemization occurred. In
//! the first example, you had two functions:
//!
//! for<'a> fn(&'a T) <: for<'b> fn(&'b T)
//!
//! and hence `&A` and `&x` were created "together". In general, the
//! intention of the skolemized names is that they are supposed to be
//! fresh names that could never be equal to anything from the outside.
//! But when inference comes into play, we might not be respecting this
//! rule.
//!
//! So the way we solve this is to add a fourth step that examines the
//! constraints that refer to skolemized names. Basically, consider a
//! non-directed version of the constraint graph. Let `Tainted(x)` be the
//! set of all things reachable from a skolemized variable `x`.
//! `Tainted(x)` should not contain any regions that existed before the
//! step at which the skolemization was performed. So this case here
//! would fail because `&x` was created alone, but is relatable to `&A`.
//!
//! ## Computing the LUB and GLB
//!
//! The paper I pointed you at is written for Haskell. It does not
//! therefore considering subtyping and in particular does not consider
//! LUB or GLB computation. We have to consider this. Here is the
//! algorithm I implemented.
//!
//! First though, let's discuss what we are trying to compute in more
//! detail. The LUB is basically the "common supertype" and the GLB is
//! "common subtype"; one catch is that the LUB should be the
//! *most-specific* common supertype and the GLB should be *most general*
//! common subtype (as opposed to any common supertype or any common
//! subtype).
//!
//! Anyway, to help clarify, here is a table containing some function
//! pairs and their LUB/GLB (for conciseness, in this table, I'm just
//! including the lifetimes here, not the rest of the types, and I'm
//! writing `fn<>` instead of `for<> fn`):
//!
//! ```
//! Type 1 Type 2 LUB GLB
//! fn<'a>('a) fn('X) fn('X) fn<'a>('a)
//! fn('a) fn('X) -- fn<'a>('a)
//! fn<'a,'b>('a, 'b) fn<'x>('x, 'x) fn<'a>('a, 'a) fn<'a,'b>('a, 'b)
//! fn<'a,'b>('a, 'b, 'a) fn<'x,'y>('x, 'y, 'y) fn<'a>('a, 'a, 'a) fn<'a,'b,'c>('a,'b,'c)
//! ```
//!
//! ### Conventions
//!
//! I use lower-case letters (e.g., `&a`) for bound regions and upper-case
//! letters for free regions (`&A`). Region variables written with a
//! dollar-sign (e.g., `$a`). I will try to remember to enumerate the
//! bound-regions on the fn type as well (e.g., `for<'a> fn(&a)`).
//!
//! ### High-level summary
//!
//! Both the LUB and the GLB algorithms work in a similar fashion. They
//! begin by replacing all bound regions (on both sides) with fresh region
//! inference variables. Therefore, both functions are converted to types
//! that contain only free regions. We can then compute the LUB/GLB in a
//! straightforward way, as described in `combine.rs`. This results in an
//! interim type T. The algorithms then examine the regions that appear
//! in T and try to, in some cases, replace them with bound regions to
//! yield the final result.
//!
//! To decide whether to replace a region `R` that appears in `T` with
//! a bound region, the algorithms make use of two bits of
//! information. First is a set `V` that contains all region
//! variables created as part of the LUB/GLB computation (roughly; see
//! `region_vars_confined_to_snapshot()` for full details). `V` will
//! contain the region variables created to replace the bound regions
//! in the input types, but it also contains 'intermediate' variables
//! created to represent the LUB/GLB of individual regions.
//! Basically, when asked to compute the LUB/GLB of a region variable
//! with another region, the inferencer cannot oblige immediately
//! since the values of that variables are not known. Therefore, it
//! creates a new variable that is related to the two regions. For
//! example, the LUB of two variables `$x` and `$y` is a fresh
//! variable `$z` that is constrained such that `$x <= $z` and `$y <=
//! $z`. So `V` will contain these intermediate variables as well.
//!
//! The other important factor in deciding how to replace a region in T is
//! the function `Tainted($r)` which, for a region variable, identifies
//! all regions that the region variable is related to in some way
//! (`Tainted()` made an appearance in the subtype computation as well).
//!
//! ### LUB
//!
//! The LUB algorithm proceeds in three steps:
//!
//! 1. Replace all bound regions (on both sides) with fresh region
//! inference variables.
//! 2. Compute the LUB "as normal", meaning compute the GLB of each
//! pair of argument types and the LUB of the return types and
//! so forth. Combine those to a new function type `F`.
//! 3. Replace each region `R` that appears in `F` as follows:
//! - Let `V` be the set of variables created during the LUB
//! computational steps 1 and 2, as described in the previous section.
//! - If `R` is not in `V`, replace `R` with itself.
//! - If `Tainted(R)` contains a region that is not in `V`,
//! replace `R` with itself.
//! - Otherwise, select the earliest variable in `Tainted(R)` that originates
//! from the left-hand side and replace `R` with the bound region that
//! this variable was a replacement for.
//!
//! So, let's work through the simplest example: `fn(&A)` and `for<'a> fn(&a)`.
//! In this case, `&a` will be replaced with `$a` and the interim LUB type
//! `fn($b)` will be computed, where `$b=GLB(&A,$a)`. Therefore, `V =
//! {$a, $b}` and `Tainted($b) = { $b, $a, &A }`. When we go to replace
//! `$b`, we find that since `&A \in Tainted($b)` is not a member of `V`,
//! we leave `$b` as is. When region inference happens, `$b` will be
//! resolved to `&A`, as we wanted.
//!
//! Let's look at a more complex one: `fn(&a, &b)` and `fn(&x, &x)`. In
//! this case, we'll end up with a (pre-replacement) LUB type of `fn(&g,
//! &h)` and a graph that looks like:
//!
//! ```
//! $a $b *--$x
//! \ \ / /
//! \ $h-* /
//! $g-----------*
//! ```
//!
//! Here `$g` and `$h` are fresh variables that are created to represent
//! the LUB/GLB of things requiring inference. This means that `V` and
//! `Tainted` will look like:
//!
//! ```
//! V = {$a, $b, $g, $h, $x}
//! Tainted($g) = Tainted($h) = { $a, $b, $h, $g, $x }
//! ```
//!
//! Therefore we replace both `$g` and `$h` with `$a`, and end up
//! with the type `fn(&a, &a)`.
//!
//! ### GLB
//!
//! The procedure for computing the GLB is similar. The difference lies
//! in computing the replacements for the various variables. For each
//! region `R` that appears in the type `F`, we again compute `Tainted(R)`
//! and examine the results:
//!
//! 1. If `R` is not in `V`, it is not replaced.
//! 2. Else, if `Tainted(R)` contains only variables in `V`, and it
//! contains exactly one variable from the LHS and one variable from
//! the RHS, then `R` can be mapped to the bound version of the
//! variable from the LHS.
//! 3. Else, if `Tainted(R)` contains no variable from the LHS and no
//! variable from the RHS, then `R` can be mapped to itself.
//! 4. Else, `R` is mapped to a fresh bound variable.
//!
//! These rules are pretty complex. Let's look at some examples to see
//! how they play out.
//!
//! Out first example was `fn(&a)` and `fn(&X)`. In this case, `&a` will
//! be replaced with `$a` and we will ultimately compute a
//! (pre-replacement) GLB type of `fn($g)` where `$g=LUB($a,&X)`.
//! Therefore, `V={$a,$g}` and `Tainted($g)={$g,$a,&X}. To find the
//! replacement for `$g` we consult the rules above:
//! - Rule (1) does not apply because `$g \in V`
//! - Rule (2) does not apply because `&X \in Tainted($g)`
//! - Rule (3) does not apply because `$a \in Tainted($g)`
//! - Hence, by rule (4), we replace `$g` with a fresh bound variable `&z`.
//! So our final result is `fn(&z)`, which is correct.
//!
//! The next example is `fn(&A)` and `fn(&Z)`. In this case, we will again
//! have a (pre-replacement) GLB of `fn(&g)`, where `$g = LUB(&A,&Z)`.
//! Therefore, `V={$g}` and `Tainted($g) = {$g, &A, &Z}`. In this case,
//! by rule (3), `$g` is mapped to itself, and hence the result is
//! `fn($g)`. This result is correct (in this case, at least), but it is
//! indicative of a case that *can* lead us into concluding that there is
//! no GLB when in fact a GLB does exist. See the section "Questionable
//! Results" below for more details.
//!
//! The next example is `fn(&a, &b)` and `fn(&c, &c)`. In this case, as
//! before, we'll end up with `F=fn($g, $h)` where `Tainted($g) =
//! Tainted($h) = {$g, $h, $a, $b, $c}`. Only rule (4) applies and hence
//! we'll select fresh bound variables `y` and `z` and wind up with
//! `fn(&y, &z)`.
//!
//! For the last example, let's consider what may seem trivial, but is
//! not: `fn(&a, &a)` and `fn(&b, &b)`. In this case, we'll get `F=fn($g,
//! $h)` where `Tainted($g) = {$g, $a, $x}` and `Tainted($h) = {$h, $a,
//! $x}`. Both of these sets contain exactly one bound variable from each
//! side, so we'll map them both to `&a`, resulting in `fn(&a, &a)`, which
//! is the desired result.
//!
//! ### Shortcomings and correctness
//!
//! You may be wondering whether this algorithm is correct. The answer is
//! "sort of". There are definitely cases where they fail to compute a
//! result even though a correct result exists. I believe, though, that
//! if they succeed, then the result is valid, and I will attempt to
//! convince you. The basic argument is that the "pre-replacement" step
//! computes a set of constraints. The replacements, then, attempt to
//! satisfy those constraints, using bound identifiers where needed.
//!
//! For now I will briefly go over the cases for LUB/GLB and identify
//! their intent:
//!
//! - LUB:
//! - The region variables that are substituted in place of bound regions
//! are intended to collect constraints on those bound regions.
//! - If Tainted(R) contains only values in V, then this region is unconstrained
//! and can therefore be generalized, otherwise it cannot.
//! - GLB:
//! - The region variables that are substituted in place of bound regions
//! are intended to collect constraints on those bound regions.
//! - If Tainted(R) contains exactly one variable from each side, and
//! only variables in V, that indicates that those two bound regions
//! must be equated.
//! - Otherwise, if Tainted(R) references any variables from left or right
//! side, then it is trying to combine a bound region with a free one or
//! multiple bound regions, so we need to select fresh bound regions.
//!
//! Sorry this is more of a shorthand to myself. I will try to write up something
//! more convincing in the future.
//!
//! #### Where are the algorithms wrong?
//!
//! - The pre-replacement computation can fail even though using a
//! bound-region would have succeeded.
//! - We will compute GLB(fn(fn($a)), fn(fn($b))) as fn($c) where $c is the
//! GLB of $a and $b. But if inference finds that $a and $b must be mapped
//! to regions without a GLB, then this is effectively a failure to compute
//! the GLB. However, the result `fn<$c>(fn($c))` is a valid GLB.

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! See doc.rs for documentation
//! See the Book for more information.
#![allow(non_camel_case_types)]
@ -46,7 +46,6 @@ use self::unify::{UnificationTable, InferCtxtMethodsForSimplyUnifiableTypes};
use self::error_reporting::ErrorReporting;
pub mod combine;
pub mod doc;
pub mod equate;
pub mod error_reporting;
pub mod glb;

View File

@ -0,0 +1,364 @@
Region inference
# Terminology
Note that we use the terms region and lifetime interchangeably,
though the term `lifetime` is preferred.
# Introduction
Region inference uses a somewhat more involved algorithm than type
inference. It is not the most efficient thing ever written though it
seems to work well enough in practice (famous last words). The reason
that we use a different algorithm is because, unlike with types, it is
impractical to hand-annotate with regions (in some cases, there aren't
even the requisite syntactic forms). So we have to get it right, and
it's worth spending more time on a more involved analysis. Moreover,
regions are a simpler case than types: they don't have aggregate
structure, for example.
Unlike normal type inference, which is similar in spirit to H-M and thus
works progressively, the region type inference works by accumulating
constraints over the course of a function. Finally, at the end of
processing a function, we process and solve the constraints all at
once.
The constraints are always of one of three possible forms:
- ConstrainVarSubVar(R_i, R_j) states that region variable R_i
must be a subregion of R_j
- ConstrainRegSubVar(R, R_i) states that the concrete region R
(which must not be a variable) must be a subregion of the variable R_i
- ConstrainVarSubReg(R_i, R) is the inverse
# Building up the constraints
Variables and constraints are created using the following methods:
- `new_region_var()` creates a new, unconstrained region variable;
- `make_subregion(R_i, R_j)` states that R_i is a subregion of R_j
- `lub_regions(R_i, R_j) -> R_k` returns a region R_k which is
the smallest region that is greater than both R_i and R_j
- `glb_regions(R_i, R_j) -> R_k` returns a region R_k which is
the greatest region that is smaller than both R_i and R_j
The actual region resolution algorithm is not entirely
obvious, though it is also not overly complex.
## Snapshotting
It is also permitted to try (and rollback) changes to the graph. This
is done by invoking `start_snapshot()`, which returns a value. Then
later you can call `rollback_to()` which undoes the work.
Alternatively, you can call `commit()` which ends all snapshots.
Snapshots can be recursive---so you can start a snapshot when another
is in progress, but only the root snapshot can "commit".
# Resolving constraints
The constraint resolution algorithm is not super complex but also not
entirely obvious. Here I describe the problem somewhat abstractly,
then describe how the current code works. There may be other, smarter
ways of doing this with which I am unfamiliar and can't be bothered to
research at the moment. - NDM
## The problem
Basically our input is a directed graph where nodes can be divided
into two categories: region variables and concrete regions. Each edge
`R -> S` in the graph represents a constraint that the region `R` is a
subregion of the region `S`.
Region variable nodes can have arbitrary degree. There is one region
variable node per region variable.
Each concrete region node is associated with some, well, concrete
region: e.g., a free lifetime, or the region for a particular scope.
Note that there may be more than one concrete region node for a
particular region value. Moreover, because of how the graph is built,
we know that all concrete region nodes have either in-degree 1 or
out-degree 1.
Before resolution begins, we build up the constraints in a hashmap
that maps `Constraint` keys to spans. During resolution, we construct
the actual `Graph` structure that we describe here.
## Our current algorithm
We divide region variables into two groups: Expanding and Contracting.
Expanding region variables are those that have a concrete region
predecessor (direct or indirect). Contracting region variables are
all others.
We first resolve the values of Expanding region variables and then
process Contracting ones. We currently use an iterative, fixed-point
procedure (but read on, I believe this could be replaced with a linear
walk). Basically we iterate over the edges in the graph, ensuring
that, if the source of the edge has a value, then this value is a
subregion of the target value. If the target does not yet have a
value, it takes the value from the source. If the target already had
a value, then the resulting value is Least Upper Bound of the old and
new values. When we are done, each Expanding node will have the
smallest region that it could possibly have and still satisfy the
constraints.
We next process the Contracting nodes. Here we again iterate over the
edges, only this time we move values from target to source (if the
source is a Contracting node). For each contracting node, we compute
its value as the GLB of all its successors. Basically contracting
nodes ensure that there is overlap between their successors; we will
ultimately infer the largest overlap possible.
# The Region Hierarchy
## Without closures
Let's first consider the region hierarchy without thinking about
closures, because they add a lot of complications. The region
hierarchy *basically* mirrors the lexical structure of the code.
There is a region for every piece of 'evaluation' that occurs, meaning
every expression, block, and pattern (patterns are considered to
"execute" by testing the value they are applied to and creating any
relevant bindings). So, for example:
fn foo(x: int, y: int) { // -+
// +------------+ // |
// | +-----+ // |
// | +-+ +-+ +-+ // |
// | | | | | | | // |
// v v v v v v v // |
let z = x + y; // |
... // |
} // -+
fn bar() { ... }
In this example, there is a region for the fn body block as a whole,
and then a subregion for the declaration of the local variable.
Within that, there are sublifetimes for the assignment pattern and
also the expression `x + y`. The expression itself has sublifetimes
for evaluating `x` and `y`.
## Function calls
Function calls are a bit tricky. I will describe how we handle them
*now* and then a bit about how we can improve them (Issue #6268).
Consider a function call like `func(expr1, expr2)`, where `func`,
`arg1`, and `arg2` are all arbitrary expressions. Currently,
we construct a region hierarchy like:
+----------------+
| |
+--+ +---+ +---+|
v v v v v vv
func(expr1, expr2)
Here you can see that the call as a whole has a region and the
function plus arguments are subregions of that. As a side-effect of
this, we get a lot of spurious errors around nested calls, in
particular when combined with `&mut` functions. For example, a call
like this one
self.foo(self.bar())
where both `foo` and `bar` are `&mut self` functions will always yield
an error.
Here is a more involved example (which is safe) so we can see what's
going on:
struct Foo { f: uint, g: uint }
...
fn add(p: &mut uint, v: uint) {
*p += v;
}
...
fn inc(p: &mut uint) -> uint {
*p += 1; *p
}
fn weird() {
let mut x: Box<Foo> = box Foo { ... };
'a: add(&mut (*x).f,
'b: inc(&mut (*x).f)) // (..)
}
The important part is the line marked `(..)` which contains a call to
`add()`. The first argument is a mutable borrow of the field `f`. The
second argument also borrows the field `f`. Now, in the current borrow
checker, the first borrow is given the lifetime of the call to
`add()`, `'a`. The second borrow is given the lifetime of `'b` of the
call to `inc()`. Because `'b` is considered to be a sublifetime of
`'a`, an error is reported since there are two co-existing mutable
borrows of the same data.
However, if we were to examine the lifetimes a bit more carefully, we
can see that this error is unnecessary. Let's examine the lifetimes
involved with `'a` in detail. We'll break apart all the steps involved
in a call expression:
'a: {
'a_arg1: let a_temp1: ... = add;
'a_arg2: let a_temp2: &'a mut uint = &'a mut (*x).f;
'a_arg3: let a_temp3: uint = {
let b_temp1: ... = inc;
let b_temp2: &'b = &'b mut (*x).f;
'b_call: b_temp1(b_temp2)
};
'a_call: a_temp1(a_temp2, a_temp3) // (**)
}
Here we see that the lifetime `'a` includes a number of substatements.
In particular, there is this lifetime I've called `'a_call` that
corresponds to the *actual execution of the function `add()`*, after
all arguments have been evaluated. There is a corresponding lifetime
`'b_call` for the execution of `inc()`. If we wanted to be precise
about it, the lifetime of the two borrows should be `'a_call` and
`'b_call` respectively, since the references that were created
will not be dereferenced except during the execution itself.
However, this model by itself is not sound. The reason is that
while the two references that are created will never be used
simultaneously, it is still true that the first reference is
*created* before the second argument is evaluated, and so even though
it will not be *dereferenced* during the evaluation of the second
argument, it can still be *invalidated* by that evaluation. Consider
this similar but unsound example:
struct Foo { f: uint, g: uint }
...
fn add(p: &mut uint, v: uint) {
*p += v;
}
...
fn consume(x: Box<Foo>) -> uint {
x.f + x.g
}
fn weird() {
let mut x: Box<Foo> = box Foo { ... };
'a: add(&mut (*x).f, consume(x)) // (..)
}
In this case, the second argument to `add` actually consumes `x`, thus
invalidating the first argument.
So, for now, we exclude the `call` lifetimes from our model.
Eventually I would like to include them, but we will have to make the
borrow checker handle this situation correctly. In particular, if
there is a reference created whose lifetime does not enclose
the borrow expression, we must issue sufficient restrictions to ensure
that the pointee remains valid.
## Adding closures
The other significant complication to the region hierarchy is
closures. I will describe here how closures should work, though some
of the work to implement this model is ongoing at the time of this
writing.
The body of closures are type-checked along with the function that
creates them. However, unlike other expressions that appear within the
function body, it is not entirely obvious when a closure body executes
with respect to the other expressions. This is because the closure
body will execute whenever the closure is called; however, we can
never know precisely when the closure will be called, especially
without some sort of alias analysis.
However, we can place some sort of limits on when the closure
executes. In particular, the type of every closure `fn:'r K` includes
a region bound `'r`. This bound indicates the maximum lifetime of that
closure; once we exit that region, the closure cannot be called
anymore. Therefore, we say that the lifetime of the closure body is a
sublifetime of the closure bound, but the closure body itself is unordered
with respect to other parts of the code.
For example, consider the following fragment of code:
'a: {
let closure: fn:'a() = || 'b: {
'c: ...
};
'd: ...
}
Here we have four lifetimes, `'a`, `'b`, `'c`, and `'d`. The closure
`closure` is bounded by the lifetime `'a`. The lifetime `'b` is the
lifetime of the closure body, and `'c` is some statement within the
closure body. Finally, `'d` is a statement within the outer block that
created the closure.
We can say that the closure body `'b` is a sublifetime of `'a` due to
the closure bound. By the usual lexical scoping conventions, the
statement `'c` is clearly a sublifetime of `'b`, and `'d` is a
sublifetime of `'d`. However, there is no ordering between `'c` and
`'d` per se (this kind of ordering between statements is actually only
an issue for dataflow; passes like the borrow checker must assume that
closures could execute at any time from the moment they are created
until they go out of scope).
### Complications due to closure bound inference
There is only one problem with the above model: in general, we do not
actually *know* the closure bounds during region inference! In fact,
closure bounds are almost always region variables! This is very tricky
because the inference system implicitly assumes that we can do things
like compute the LUB of two scoped lifetimes without needing to know
the values of any variables.
Here is an example to illustrate the problem:
fn identify<T>(x: T) -> T { x }
fn foo() { // 'foo is the function body
'a: {
let closure = identity(|| 'b: {
'c: ...
});
'd: closure();
}
'e: ...;
}
In this example, the closure bound is not explicit. At compile time,
we will create a region variable (let's call it `V0`) to represent the
closure bound.
The primary difficulty arises during the constraint propagation phase.
Imagine there is some variable with incoming edges from `'c` and `'d`.
This means that the value of the variable must be `LUB('c,
'd)`. However, without knowing what the closure bound `V0` is, we
can't compute the LUB of `'c` and `'d`! Any we don't know the closure
bound until inference is done.
The solution is to rely on the fixed point nature of inference.
Basically, when we must compute `LUB('c, 'd)`, we just use the current
value for `V0` as the closure's bound. If `V0`'s binding should
change, then we will do another round of inference, and the result of
`LUB('c, 'd)` will change.
One minor implication of this is that the graph does not in fact track
the full set of dependencies between edges. We cannot easily know
whether the result of a LUB computation will change, since there may
be indirect dependencies on other variables that are not reflected on
the graph. Therefore, we must *always* iterate over all edges when
doing the fixed point calculation, not just those adjacent to nodes
whose values have changed.
Were it not for this requirement, we could in fact avoid fixed-point
iteration altogether. In that universe, we could instead first
identify and remove strongly connected components (SCC) in the graph.
Note that such components must consist solely of region variables; all
of these variables can effectively be unified into a single variable.
Once SCCs are removed, we are left with a DAG. At this point, we
could walk the DAG in topological order once to compute the expanding
nodes, and again in reverse topological order to compute the
contracting nodes. However, as I said, this does not work given the
current treatment of closure bounds, but perhaps in the future we can
address this problem somehow and make region inference somewhat more
efficient. Note that this is solely a matter of performance, not
expressiveness.
### Skolemization
For a discussion on skolemization and higher-ranked subtyping, please
see the module `middle::infer::higher_ranked::doc`.

View File

@ -1,374 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Region inference module.
//!
//! # Terminology
//!
//! Note that we use the terms region and lifetime interchangeably,
//! though the term `lifetime` is preferred.
//!
//! # Introduction
//!
//! Region inference uses a somewhat more involved algorithm than type
//! inference. It is not the most efficient thing ever written though it
//! seems to work well enough in practice (famous last words). The reason
//! that we use a different algorithm is because, unlike with types, it is
//! impractical to hand-annotate with regions (in some cases, there aren't
//! even the requisite syntactic forms). So we have to get it right, and
//! it's worth spending more time on a more involved analysis. Moreover,
//! regions are a simpler case than types: they don't have aggregate
//! structure, for example.
//!
//! Unlike normal type inference, which is similar in spirit to H-M and thus
//! works progressively, the region type inference works by accumulating
//! constraints over the course of a function. Finally, at the end of
//! processing a function, we process and solve the constraints all at
//! once.
//!
//! The constraints are always of one of three possible forms:
//!
//! - ConstrainVarSubVar(R_i, R_j) states that region variable R_i
//! must be a subregion of R_j
//! - ConstrainRegSubVar(R, R_i) states that the concrete region R
//! (which must not be a variable) must be a subregion of the variable R_i
//! - ConstrainVarSubReg(R_i, R) is the inverse
//!
//! # Building up the constraints
//!
//! Variables and constraints are created using the following methods:
//!
//! - `new_region_var()` creates a new, unconstrained region variable;
//! - `make_subregion(R_i, R_j)` states that R_i is a subregion of R_j
//! - `lub_regions(R_i, R_j) -> R_k` returns a region R_k which is
//! the smallest region that is greater than both R_i and R_j
//! - `glb_regions(R_i, R_j) -> R_k` returns a region R_k which is
//! the greatest region that is smaller than both R_i and R_j
//!
//! The actual region resolution algorithm is not entirely
//! obvious, though it is also not overly complex.
//!
//! ## Snapshotting
//!
//! It is also permitted to try (and rollback) changes to the graph. This
//! is done by invoking `start_snapshot()`, which returns a value. Then
//! later you can call `rollback_to()` which undoes the work.
//! Alternatively, you can call `commit()` which ends all snapshots.
//! Snapshots can be recursive---so you can start a snapshot when another
//! is in progress, but only the root snapshot can "commit".
//!
//! # Resolving constraints
//!
//! The constraint resolution algorithm is not super complex but also not
//! entirely obvious. Here I describe the problem somewhat abstractly,
//! then describe how the current code works. There may be other, smarter
//! ways of doing this with which I am unfamiliar and can't be bothered to
//! research at the moment. - NDM
//!
//! ## The problem
//!
//! Basically our input is a directed graph where nodes can be divided
//! into two categories: region variables and concrete regions. Each edge
//! `R -> S` in the graph represents a constraint that the region `R` is a
//! subregion of the region `S`.
//!
//! Region variable nodes can have arbitrary degree. There is one region
//! variable node per region variable.
//!
//! Each concrete region node is associated with some, well, concrete
//! region: e.g., a free lifetime, or the region for a particular scope.
//! Note that there may be more than one concrete region node for a
//! particular region value. Moreover, because of how the graph is built,
//! we know that all concrete region nodes have either in-degree 1 or
//! out-degree 1.
//!
//! Before resolution begins, we build up the constraints in a hashmap
//! that maps `Constraint` keys to spans. During resolution, we construct
//! the actual `Graph` structure that we describe here.
//!
//! ## Our current algorithm
//!
//! We divide region variables into two groups: Expanding and Contracting.
//! Expanding region variables are those that have a concrete region
//! predecessor (direct or indirect). Contracting region variables are
//! all others.
//!
//! We first resolve the values of Expanding region variables and then
//! process Contracting ones. We currently use an iterative, fixed-point
//! procedure (but read on, I believe this could be replaced with a linear
//! walk). Basically we iterate over the edges in the graph, ensuring
//! that, if the source of the edge has a value, then this value is a
//! subregion of the target value. If the target does not yet have a
//! value, it takes the value from the source. If the target already had
//! a value, then the resulting value is Least Upper Bound of the old and
//! new values. When we are done, each Expanding node will have the
//! smallest region that it could possibly have and still satisfy the
//! constraints.
//!
//! We next process the Contracting nodes. Here we again iterate over the
//! edges, only this time we move values from target to source (if the
//! source is a Contracting node). For each contracting node, we compute
//! its value as the GLB of all its successors. Basically contracting
//! nodes ensure that there is overlap between their successors; we will
//! ultimately infer the largest overlap possible.
//!
//! # The Region Hierarchy
//!
//! ## Without closures
//!
//! Let's first consider the region hierarchy without thinking about
//! closures, because they add a lot of complications. The region
//! hierarchy *basically* mirrors the lexical structure of the code.
//! There is a region for every piece of 'evaluation' that occurs, meaning
//! every expression, block, and pattern (patterns are considered to
//! "execute" by testing the value they are applied to and creating any
//! relevant bindings). So, for example:
//!
//! fn foo(x: int, y: int) { // -+
//! // +------------+ // |
//! // | +-----+ // |
//! // | +-+ +-+ +-+ // |
//! // | | | | | | | // |
//! // v v v v v v v // |
//! let z = x + y; // |
//! ... // |
//! } // -+
//!
//! fn bar() { ... }
//!
//! In this example, there is a region for the fn body block as a whole,
//! and then a subregion for the declaration of the local variable.
//! Within that, there are sublifetimes for the assignment pattern and
//! also the expression `x + y`. The expression itself has sublifetimes
//! for evaluating `x` and `y`.
//!
//! ## Function calls
//!
//! Function calls are a bit tricky. I will describe how we handle them
//! *now* and then a bit about how we can improve them (Issue #6268).
//!
//! Consider a function call like `func(expr1, expr2)`, where `func`,
//! `arg1`, and `arg2` are all arbitrary expressions. Currently,
//! we construct a region hierarchy like:
//!
//! +----------------+
//! | |
//! +--+ +---+ +---+|
//! v v v v v vv
//! func(expr1, expr2)
//!
//! Here you can see that the call as a whole has a region and the
//! function plus arguments are subregions of that. As a side-effect of
//! this, we get a lot of spurious errors around nested calls, in
//! particular when combined with `&mut` functions. For example, a call
//! like this one
//!
//! self.foo(self.bar())
//!
//! where both `foo` and `bar` are `&mut self` functions will always yield
//! an error.
//!
//! Here is a more involved example (which is safe) so we can see what's
//! going on:
//!
//! struct Foo { f: uint, g: uint }
//! ...
//! fn add(p: &mut uint, v: uint) {
//! *p += v;
//! }
//! ...
//! fn inc(p: &mut uint) -> uint {
//! *p += 1; *p
//! }
//! fn weird() {
//! let mut x: Box<Foo> = box Foo { ... };
//! 'a: add(&mut (*x).f,
//! 'b: inc(&mut (*x).f)) // (..)
//! }
//!
//! The important part is the line marked `(..)` which contains a call to
//! `add()`. The first argument is a mutable borrow of the field `f`. The
//! second argument also borrows the field `f`. Now, in the current borrow
//! checker, the first borrow is given the lifetime of the call to
//! `add()`, `'a`. The second borrow is given the lifetime of `'b` of the
//! call to `inc()`. Because `'b` is considered to be a sublifetime of
//! `'a`, an error is reported since there are two co-existing mutable
//! borrows of the same data.
//!
//! However, if we were to examine the lifetimes a bit more carefully, we
//! can see that this error is unnecessary. Let's examine the lifetimes
//! involved with `'a` in detail. We'll break apart all the steps involved
//! in a call expression:
//!
//! 'a: {
//! 'a_arg1: let a_temp1: ... = add;
//! 'a_arg2: let a_temp2: &'a mut uint = &'a mut (*x).f;
//! 'a_arg3: let a_temp3: uint = {
//! let b_temp1: ... = inc;
//! let b_temp2: &'b = &'b mut (*x).f;
//! 'b_call: b_temp1(b_temp2)
//! };
//! 'a_call: a_temp1(a_temp2, a_temp3) // (**)
//! }
//!
//! Here we see that the lifetime `'a` includes a number of substatements.
//! In particular, there is this lifetime I've called `'a_call` that
//! corresponds to the *actual execution of the function `add()`*, after
//! all arguments have been evaluated. There is a corresponding lifetime
//! `'b_call` for the execution of `inc()`. If we wanted to be precise
//! about it, the lifetime of the two borrows should be `'a_call` and
//! `'b_call` respectively, since the references that were created
//! will not be dereferenced except during the execution itself.
//!
//! However, this model by itself is not sound. The reason is that
//! while the two references that are created will never be used
//! simultaneously, it is still true that the first reference is
//! *created* before the second argument is evaluated, and so even though
//! it will not be *dereferenced* during the evaluation of the second
//! argument, it can still be *invalidated* by that evaluation. Consider
//! this similar but unsound example:
//!
//! struct Foo { f: uint, g: uint }
//! ...
//! fn add(p: &mut uint, v: uint) {
//! *p += v;
//! }
//! ...
//! fn consume(x: Box<Foo>) -> uint {
//! x.f + x.g
//! }
//! fn weird() {
//! let mut x: Box<Foo> = box Foo { ... };
//! 'a: add(&mut (*x).f, consume(x)) // (..)
//! }
//!
//! In this case, the second argument to `add` actually consumes `x`, thus
//! invalidating the first argument.
//!
//! So, for now, we exclude the `call` lifetimes from our model.
//! Eventually I would like to include them, but we will have to make the
//! borrow checker handle this situation correctly. In particular, if
//! there is a reference created whose lifetime does not enclose
//! the borrow expression, we must issue sufficient restrictions to ensure
//! that the pointee remains valid.
//!
//! ## Adding closures
//!
//! The other significant complication to the region hierarchy is
//! closures. I will describe here how closures should work, though some
//! of the work to implement this model is ongoing at the time of this
//! writing.
//!
//! The body of closures are type-checked along with the function that
//! creates them. However, unlike other expressions that appear within the
//! function body, it is not entirely obvious when a closure body executes
//! with respect to the other expressions. This is because the closure
//! body will execute whenever the closure is called; however, we can
//! never know precisely when the closure will be called, especially
//! without some sort of alias analysis.
//!
//! However, we can place some sort of limits on when the closure
//! executes. In particular, the type of every closure `fn:'r K` includes
//! a region bound `'r`. This bound indicates the maximum lifetime of that
//! closure; once we exit that region, the closure cannot be called
//! anymore. Therefore, we say that the lifetime of the closure body is a
//! sublifetime of the closure bound, but the closure body itself is unordered
//! with respect to other parts of the code.
//!
//! For example, consider the following fragment of code:
//!
//! 'a: {
//! let closure: fn:'a() = || 'b: {
//! 'c: ...
//! };
//! 'd: ...
//! }
//!
//! Here we have four lifetimes, `'a`, `'b`, `'c`, and `'d`. The closure
//! `closure` is bounded by the lifetime `'a`. The lifetime `'b` is the
//! lifetime of the closure body, and `'c` is some statement within the
//! closure body. Finally, `'d` is a statement within the outer block that
//! created the closure.
//!
//! We can say that the closure body `'b` is a sublifetime of `'a` due to
//! the closure bound. By the usual lexical scoping conventions, the
//! statement `'c` is clearly a sublifetime of `'b`, and `'d` is a
//! sublifetime of `'d`. However, there is no ordering between `'c` and
//! `'d` per se (this kind of ordering between statements is actually only
//! an issue for dataflow; passes like the borrow checker must assume that
//! closures could execute at any time from the moment they are created
//! until they go out of scope).
//!
//! ### Complications due to closure bound inference
//!
//! There is only one problem with the above model: in general, we do not
//! actually *know* the closure bounds during region inference! In fact,
//! closure bounds are almost always region variables! This is very tricky
//! because the inference system implicitly assumes that we can do things
//! like compute the LUB of two scoped lifetimes without needing to know
//! the values of any variables.
//!
//! Here is an example to illustrate the problem:
//!
//! fn identify<T>(x: T) -> T { x }
//!
//! fn foo() { // 'foo is the function body
//! 'a: {
//! let closure = identity(|| 'b: {
//! 'c: ...
//! });
//! 'd: closure();
//! }
//! 'e: ...;
//! }
//!
//! In this example, the closure bound is not explicit. At compile time,
//! we will create a region variable (let's call it `V0`) to represent the
//! closure bound.
//!
//! The primary difficulty arises during the constraint propagation phase.
//! Imagine there is some variable with incoming edges from `'c` and `'d`.
//! This means that the value of the variable must be `LUB('c,
//! 'd)`. However, without knowing what the closure bound `V0` is, we
//! can't compute the LUB of `'c` and `'d`! Any we don't know the closure
//! bound until inference is done.
//!
//! The solution is to rely on the fixed point nature of inference.
//! Basically, when we must compute `LUB('c, 'd)`, we just use the current
//! value for `V0` as the closure's bound. If `V0`'s binding should
//! change, then we will do another round of inference, and the result of
//! `LUB('c, 'd)` will change.
//!
//! One minor implication of this is that the graph does not in fact track
//! the full set of dependencies between edges. We cannot easily know
//! whether the result of a LUB computation will change, since there may
//! be indirect dependencies on other variables that are not reflected on
//! the graph. Therefore, we must *always* iterate over all edges when
//! doing the fixed point calculation, not just those adjacent to nodes
//! whose values have changed.
//!
//! Were it not for this requirement, we could in fact avoid fixed-point
//! iteration altogether. In that universe, we could instead first
//! identify and remove strongly connected components (SCC) in the graph.
//! Note that such components must consist solely of region variables; all
//! of these variables can effectively be unified into a single variable.
//! Once SCCs are removed, we are left with a DAG. At this point, we
//! could walk the DAG in topological order once to compute the expanding
//! nodes, and again in reverse topological order to compute the
//! contracting nodes. However, as I said, this does not work given the
//! current treatment of closure bounds, but perhaps in the future we can
//! address this problem somehow and make region inference somewhat more
//! efficient. Note that this is solely a matter of performance, not
//! expressiveness.
//!
//! ### Skolemization
//!
//! For a discussion on skolemization and higher-ranked subtyping, please
//! see the module `middle::infer::higher_ranked::doc`.

View File

@ -38,7 +38,6 @@ use std::iter::repeat;
use std::u32;
use syntax::ast;
mod doc;
mod graphviz;
// A constraint that influences the inference process.

View File

@ -1,15 +1,3 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*!
# TRAIT RESOLUTION
This document describes the general process and points out some non-obvious
@ -440,5 +428,3 @@ We used to try and draw finer-grained distinctions, but that led to a
serious of annoying and weird bugs like #22019 and #18290. This simple
rule seems to be pretty clearly safe and also still retains a very
high hit rate (~95% when compiling rustc).
*/

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Trait Resolution. See doc.rs.
//! Trait Resolution. See the Book for more.
pub use self::SelectionError::*;
pub use self::FulfillmentErrorCode::*;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! See doc.rs for a thorough explanation of the borrow checker
//! See The Book chapter on the borrow checker for more details.
#![allow(non_camel_case_types)]
@ -41,8 +41,6 @@ use syntax::visit;
use syntax::visit::{Visitor, FnKind};
use syntax::ast::{FnDecl, Block, NodeId};
pub mod doc;
pub mod check_loans;
pub mod gather_loans;

View File

@ -8,8 +8,111 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Code pertaining to cleanup of temporaries as well as execution of
//! drop glue. See discussion in `doc.rs` for a high-level summary.
//! ## The Cleanup module
//!
//! The cleanup module tracks what values need to be cleaned up as scopes
//! are exited, either via panic or just normal control flow. The basic
//! idea is that the function context maintains a stack of cleanup scopes
//! that are pushed/popped as we traverse the AST tree. There is typically
//! at least one cleanup scope per AST node; some AST nodes may introduce
//! additional temporary scopes.
//!
//! Cleanup items can be scheduled into any of the scopes on the stack.
//! Typically, when a scope is popped, we will also generate the code for
//! each of its cleanups at that time. This corresponds to a normal exit
//! from a block (for example, an expression completing evaluation
//! successfully without panic). However, it is also possible to pop a
//! block *without* executing its cleanups; this is typically used to
//! guard intermediate values that must be cleaned up on panic, but not
//! if everything goes right. See the section on custom scopes below for
//! more details.
//!
//! Cleanup scopes come in three kinds:
//!
//! - **AST scopes:** each AST node in a function body has a corresponding
//! AST scope. We push the AST scope when we start generate code for an AST
//! node and pop it once the AST node has been fully generated.
//! - **Loop scopes:** loops have an additional cleanup scope. Cleanups are
//! never scheduled into loop scopes; instead, they are used to record the
//! basic blocks that we should branch to when a `continue` or `break` statement
//! is encountered.
//! - **Custom scopes:** custom scopes are typically used to ensure cleanup
//! of intermediate values.
//!
//! ### When to schedule cleanup
//!
//! Although the cleanup system is intended to *feel* fairly declarative,
//! it's still important to time calls to `schedule_clean()` correctly.
//! Basically, you should not schedule cleanup for memory until it has
//! been initialized, because if an unwind should occur before the memory
//! is fully initialized, then the cleanup will run and try to free or
//! drop uninitialized memory. If the initialization itself produces
//! byproducts that need to be freed, then you should use temporary custom
//! scopes to ensure that those byproducts will get freed on unwind. For
//! example, an expression like `box foo()` will first allocate a box in the
//! heap and then call `foo()` -- if `foo()` should panic, this box needs
//! to be *shallowly* freed.
//!
//! ### Long-distance jumps
//!
//! In addition to popping a scope, which corresponds to normal control
//! flow exiting the scope, we may also *jump out* of a scope into some
//! earlier scope on the stack. This can occur in response to a `return`,
//! `break`, or `continue` statement, but also in response to panic. In
//! any of these cases, we will generate a series of cleanup blocks for
//! each of the scopes that is exited. So, if the stack contains scopes A
//! ... Z, and we break out of a loop whose corresponding cleanup scope is
//! X, we would generate cleanup blocks for the cleanups in X, Y, and Z.
//! After cleanup is done we would branch to the exit point for scope X.
//! But if panic should occur, we would generate cleanups for all the
//! scopes from A to Z and then resume the unwind process afterwards.
//!
//! To avoid generating tons of code, we cache the cleanup blocks that we
//! create for breaks, returns, unwinds, and other jumps. Whenever a new
//! cleanup is scheduled, though, we must clear these cached blocks. A
//! possible improvement would be to keep the cached blocks but simply
//! generate a new block which performs the additional cleanup and then
//! branches to the existing cached blocks.
//!
//! ### AST and loop cleanup scopes
//!
//! AST cleanup scopes are pushed when we begin and end processing an AST
//! node. They are used to house cleanups related to rvalue temporary that
//! get referenced (e.g., due to an expression like `&Foo()`). Whenever an
//! AST scope is popped, we always trans all the cleanups, adding the cleanup
//! code after the postdominator of the AST node.
//!
//! AST nodes that represent breakable loops also push a loop scope; the
//! loop scope never has any actual cleanups, it's just used to point to
//! the basic blocks where control should flow after a "continue" or
//! "break" statement. Popping a loop scope never generates code.
//!
//! ### Custom cleanup scopes
//!
//! Custom cleanup scopes are used for a variety of purposes. The most
//! common though is to handle temporary byproducts, where cleanup only
//! needs to occur on panic. The general strategy is to push a custom
//! cleanup scope, schedule *shallow* cleanups into the custom scope, and
//! then pop the custom scope (without transing the cleanups) when
//! execution succeeds normally. This way the cleanups are only trans'd on
//! unwind, and only up until the point where execution succeeded, at
//! which time the complete value should be stored in an lvalue or some
//! other place where normal cleanup applies.
//!
//! To spell it out, here is an example. Imagine an expression `box expr`.
//! We would basically:
//!
//! 1. Push a custom cleanup scope C.
//! 2. Allocate the box.
//! 3. Schedule a shallow free in the scope C.
//! 4. Trans `expr` into the box.
//! 5. Pop the scope C.
//! 6. Return the box as an rvalue.
//!
//! This way, if a panic occurs while transing `expr`, the custom
//! cleanup scope C is pushed and hence the box will be freed. The trans
//! code for `expr` itself is responsible for freeing any other byproducts
//! that may be in play.
pub use self::ScopeId::*;
pub use self::CleanupScopeKind::*;

View File

@ -8,8 +8,97 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! See the section on datums in `doc.rs` for an overview of what Datums are and how they are
//! intended to be used.
//! ## The Datum module
//!
//! A `Datum` encapsulates the result of evaluating a Rust expression. It
//! contains a `ValueRef` indicating the result, a `Ty` describing
//! the Rust type, but also a *kind*. The kind indicates whether the datum
//! has cleanup scheduled (lvalue) or not (rvalue) and -- in the case of
//! rvalues -- whether or not the value is "by ref" or "by value".
//!
//! The datum API is designed to try and help you avoid memory errors like
//! forgetting to arrange cleanup or duplicating a value. The type of the
//! datum incorporates the kind, and thus reflects whether it has cleanup
//! scheduled:
//!
//! - `Datum<Lvalue>` -- by ref, cleanup scheduled
//! - `Datum<Rvalue>` -- by value or by ref, no cleanup scheduled
//! - `Datum<Expr>` -- either `Datum<Lvalue>` or `Datum<Rvalue>`
//!
//! Rvalue and expr datums are noncopyable, and most of the methods on
//! datums consume the datum itself (with some notable exceptions). This
//! reflects the fact that datums may represent affine values which ought
//! to be consumed exactly once, and if you were to try to (for example)
//! store an affine value multiple times, you would be duplicating it,
//! which would certainly be a bug.
//!
//! Some of the datum methods, however, are designed to work only on
//! copyable values such as ints or pointers. Those methods may borrow the
//! datum (`&self`) rather than consume it, but they always include
//! assertions on the type of the value represented to check that this
//! makes sense. An example is `shallow_copy()`, which duplicates
//! a datum value.
//!
//! Translating an expression always yields a `Datum<Expr>` result, but
//! the methods `to_[lr]value_datum()` can be used to coerce a
//! `Datum<Expr>` into a `Datum<Lvalue>` or `Datum<Rvalue>` as
//! needed. Coercing to an lvalue is fairly common, and generally occurs
//! whenever it is necessary to inspect a value and pull out its
//! subcomponents (for example, a match, or indexing expression). Coercing
//! to an rvalue is more unusual; it occurs when moving values from place
//! to place, such as in an assignment expression or parameter passing.
//!
//! ### Lvalues in detail
//!
//! An lvalue datum is one for which cleanup has been scheduled. Lvalue
//! datums are always located in memory, and thus the `ValueRef` for an
//! LLVM value is always a pointer to the actual Rust value. This means
//! that if the Datum has a Rust type of `int`, then the LLVM type of the
//! `ValueRef` will be `int*` (pointer to int).
//!
//! Because lvalues already have cleanups scheduled, the memory must be
//! zeroed to prevent the cleanup from taking place (presuming that the
//! Rust type needs drop in the first place, otherwise it doesn't
//! matter). The Datum code automatically performs this zeroing when the
//! value is stored to a new location, for example.
//!
//! Lvalues usually result from evaluating lvalue expressions. For
//! example, evaluating a local variable `x` yields an lvalue, as does a
//! reference to a field like `x.f` or an index `x[i]`.
//!
//! Lvalue datums can also arise by *converting* an rvalue into an lvalue.
//! This is done with the `to_lvalue_datum` method defined on
//! `Datum<Expr>`. Basically this method just schedules cleanup if the
//! datum is an rvalue, possibly storing the value into a stack slot first
//! if needed. Converting rvalues into lvalues occurs in constructs like
//! `&foo()` or `match foo() { ref x => ... }`, where the user is
//! implicitly requesting a temporary.
//!
//! Somewhat surprisingly, not all lvalue expressions yield lvalue datums
//! when trans'd. Ultimately the reason for this is to micro-optimize
//! the resulting LLVM. For example, consider the following code:
//!
//! fn foo() -> Box<int> { ... }
//! let x = *foo();
//!
//! The expression `*foo()` is an lvalue, but if you invoke `expr::trans`,
//! it will return an rvalue datum. See `deref_once` in expr.rs for
//! more details.
//!
//! ### Rvalues in detail
//!
//! Rvalues datums are values with no cleanup scheduled. One must be
//! careful with rvalue datums to ensure that cleanup is properly
//! arranged, usually by converting to an lvalue datum or by invoking the
//! `add_clean` method.
//!
//! ### Scratch datums
//!
//! Sometimes you need some temporary scratch space. The functions
//! `[lr]value_scratch_datum()` can be used to get temporary stack
//! space. As their name suggests, they yield lvalues and rvalues
//! respectively. That is, the slot from `lvalue_scratch_datum` will have
//! cleanup arranged, and the slot from `rvalue_scratch_datum` does not.
pub use self::Expr::*;
pub use self::RvalueMode::*;

View File

@ -1,233 +0,0 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! # Documentation for the trans module
//!
//! This module contains high-level summaries of how the various modules
//! in trans work. It is a work in progress. For detailed comments,
//! naturally, you can refer to the individual modules themselves.
//!
//! ## The Expr module
//!
//! The expr module handles translation of expressions. The most general
//! translation routine is `trans()`, which will translate an expression
//! into a datum. `trans_into()` is also available, which will translate
//! an expression and write the result directly into memory, sometimes
//! avoiding the need for a temporary stack slot. Finally,
//! `trans_to_lvalue()` is available if you'd like to ensure that the
//! result has cleanup scheduled.
//!
//! Internally, each of these functions dispatches to various other
//! expression functions depending on the kind of expression. We divide
//! up expressions into:
//!
//! - **Datum expressions:** Those that most naturally yield values.
//! Examples would be `22`, `box x`, or `a + b` (when not overloaded).
//! - **DPS expressions:** Those that most naturally write into a location
//! in memory. Examples would be `foo()` or `Point { x: 3, y: 4 }`.
//! - **Statement expressions:** That that do not generate a meaningful
//! result. Examples would be `while { ... }` or `return 44`.
//!
//! ## The Datum module
//!
//! A `Datum` encapsulates the result of evaluating a Rust expression. It
//! contains a `ValueRef` indicating the result, a `Ty` describing
//! the Rust type, but also a *kind*. The kind indicates whether the datum
//! has cleanup scheduled (lvalue) or not (rvalue) and -- in the case of
//! rvalues -- whether or not the value is "by ref" or "by value".
//!
//! The datum API is designed to try and help you avoid memory errors like
//! forgetting to arrange cleanup or duplicating a value. The type of the
//! datum incorporates the kind, and thus reflects whether it has cleanup
//! scheduled:
//!
//! - `Datum<Lvalue>` -- by ref, cleanup scheduled
//! - `Datum<Rvalue>` -- by value or by ref, no cleanup scheduled
//! - `Datum<Expr>` -- either `Datum<Lvalue>` or `Datum<Rvalue>`
//!
//! Rvalue and expr datums are noncopyable, and most of the methods on
//! datums consume the datum itself (with some notable exceptions). This
//! reflects the fact that datums may represent affine values which ought
//! to be consumed exactly once, and if you were to try to (for example)
//! store an affine value multiple times, you would be duplicating it,
//! which would certainly be a bug.
//!
//! Some of the datum methods, however, are designed to work only on
//! copyable values such as ints or pointers. Those methods may borrow the
//! datum (`&self`) rather than consume it, but they always include
//! assertions on the type of the value represented to check that this
//! makes sense. An example is `shallow_copy()`, which duplicates
//! a datum value.
//!
//! Translating an expression always yields a `Datum<Expr>` result, but
//! the methods `to_[lr]value_datum()` can be used to coerce a
//! `Datum<Expr>` into a `Datum<Lvalue>` or `Datum<Rvalue>` as
//! needed. Coercing to an lvalue is fairly common, and generally occurs
//! whenever it is necessary to inspect a value and pull out its
//! subcomponents (for example, a match, or indexing expression). Coercing
//! to an rvalue is more unusual; it occurs when moving values from place
//! to place, such as in an assignment expression or parameter passing.
//!
//! ### Lvalues in detail
//!
//! An lvalue datum is one for which cleanup has been scheduled. Lvalue
//! datums are always located in memory, and thus the `ValueRef` for an
//! LLVM value is always a pointer to the actual Rust value. This means
//! that if the Datum has a Rust type of `int`, then the LLVM type of the
//! `ValueRef` will be `int*` (pointer to int).
//!
//! Because lvalues already have cleanups scheduled, the memory must be
//! zeroed to prevent the cleanup from taking place (presuming that the
//! Rust type needs drop in the first place, otherwise it doesn't
//! matter). The Datum code automatically performs this zeroing when the
//! value is stored to a new location, for example.
//!
//! Lvalues usually result from evaluating lvalue expressions. For
//! example, evaluating a local variable `x` yields an lvalue, as does a
//! reference to a field like `x.f` or an index `x[i]`.
//!
//! Lvalue datums can also arise by *converting* an rvalue into an lvalue.
//! This is done with the `to_lvalue_datum` method defined on
//! `Datum<Expr>`. Basically this method just schedules cleanup if the
//! datum is an rvalue, possibly storing the value into a stack slot first
//! if needed. Converting rvalues into lvalues occurs in constructs like
//! `&foo()` or `match foo() { ref x => ... }`, where the user is
//! implicitly requesting a temporary.
//!
//! Somewhat surprisingly, not all lvalue expressions yield lvalue datums
//! when trans'd. Ultimately the reason for this is to micro-optimize
//! the resulting LLVM. For example, consider the following code:
//!
//! fn foo() -> Box<int> { ... }
//! let x = *foo();
//!
//! The expression `*foo()` is an lvalue, but if you invoke `expr::trans`,
//! it will return an rvalue datum. See `deref_once` in expr.rs for
//! more details.
//!
//! ### Rvalues in detail
//!
//! Rvalues datums are values with no cleanup scheduled. One must be
//! careful with rvalue datums to ensure that cleanup is properly
//! arranged, usually by converting to an lvalue datum or by invoking the
//! `add_clean` method.
//!
//! ### Scratch datums
//!
//! Sometimes you need some temporary scratch space. The functions
//! `[lr]value_scratch_datum()` can be used to get temporary stack
//! space. As their name suggests, they yield lvalues and rvalues
//! respectively. That is, the slot from `lvalue_scratch_datum` will have
//! cleanup arranged, and the slot from `rvalue_scratch_datum` does not.
//!
//! ## The Cleanup module
//!
//! The cleanup module tracks what values need to be cleaned up as scopes
//! are exited, either via panic or just normal control flow. The basic
//! idea is that the function context maintains a stack of cleanup scopes
//! that are pushed/popped as we traverse the AST tree. There is typically
//! at least one cleanup scope per AST node; some AST nodes may introduce
//! additional temporary scopes.
//!
//! Cleanup items can be scheduled into any of the scopes on the stack.
//! Typically, when a scope is popped, we will also generate the code for
//! each of its cleanups at that time. This corresponds to a normal exit
//! from a block (for example, an expression completing evaluation
//! successfully without panic). However, it is also possible to pop a
//! block *without* executing its cleanups; this is typically used to
//! guard intermediate values that must be cleaned up on panic, but not
//! if everything goes right. See the section on custom scopes below for
//! more details.
//!
//! Cleanup scopes come in three kinds:
//! - **AST scopes:** each AST node in a function body has a corresponding
//! AST scope. We push the AST scope when we start generate code for an AST
//! node and pop it once the AST node has been fully generated.
//! - **Loop scopes:** loops have an additional cleanup scope. Cleanups are
//! never scheduled into loop scopes; instead, they are used to record the
//! basic blocks that we should branch to when a `continue` or `break` statement
//! is encountered.
//! - **Custom scopes:** custom scopes are typically used to ensure cleanup
//! of intermediate values.
//!
//! ### When to schedule cleanup
//!
//! Although the cleanup system is intended to *feel* fairly declarative,
//! it's still important to time calls to `schedule_clean()` correctly.
//! Basically, you should not schedule cleanup for memory until it has
//! been initialized, because if an unwind should occur before the memory
//! is fully initialized, then the cleanup will run and try to free or
//! drop uninitialized memory. If the initialization itself produces
//! byproducts that need to be freed, then you should use temporary custom
//! scopes to ensure that those byproducts will get freed on unwind. For
//! example, an expression like `box foo()` will first allocate a box in the
//! heap and then call `foo()` -- if `foo()` should panic, this box needs
//! to be *shallowly* freed.
//!
//! ### Long-distance jumps
//!
//! In addition to popping a scope, which corresponds to normal control
//! flow exiting the scope, we may also *jump out* of a scope into some
//! earlier scope on the stack. This can occur in response to a `return`,
//! `break`, or `continue` statement, but also in response to panic. In
//! any of these cases, we will generate a series of cleanup blocks for
//! each of the scopes that is exited. So, if the stack contains scopes A
//! ... Z, and we break out of a loop whose corresponding cleanup scope is
//! X, we would generate cleanup blocks for the cleanups in X, Y, and Z.
//! After cleanup is done we would branch to the exit point for scope X.
//! But if panic should occur, we would generate cleanups for all the
//! scopes from A to Z and then resume the unwind process afterwards.
//!
//! To avoid generating tons of code, we cache the cleanup blocks that we
//! create for breaks, returns, unwinds, and other jumps. Whenever a new
//! cleanup is scheduled, though, we must clear these cached blocks. A
//! possible improvement would be to keep the cached blocks but simply
//! generate a new block which performs the additional cleanup and then
//! branches to the existing cached blocks.
//!
//! ### AST and loop cleanup scopes
//!
//! AST cleanup scopes are pushed when we begin and end processing an AST
//! node. They are used to house cleanups related to rvalue temporary that
//! get referenced (e.g., due to an expression like `&Foo()`). Whenever an
//! AST scope is popped, we always trans all the cleanups, adding the cleanup
//! code after the postdominator of the AST node.
//!
//! AST nodes that represent breakable loops also push a loop scope; the
//! loop scope never has any actual cleanups, it's just used to point to
//! the basic blocks where control should flow after a "continue" or
//! "break" statement. Popping a loop scope never generates code.
//!
//! ### Custom cleanup scopes
//!
//! Custom cleanup scopes are used for a variety of purposes. The most
//! common though is to handle temporary byproducts, where cleanup only
//! needs to occur on panic. The general strategy is to push a custom
//! cleanup scope, schedule *shallow* cleanups into the custom scope, and
//! then pop the custom scope (without transing the cleanups) when
//! execution succeeds normally. This way the cleanups are only trans'd on
//! unwind, and only up until the point where execution succeeded, at
//! which time the complete value should be stored in an lvalue or some
//! other place where normal cleanup applies.
//!
//! To spell it out, here is an example. Imagine an expression `box expr`.
//! We would basically:
//!
//! 1. Push a custom cleanup scope C.
//! 2. Allocate the box.
//! 3. Schedule a shallow free in the scope C.
//! 4. Trans `expr` into the box.
//! 5. Pop the scope C.
//! 6. Return the box as an rvalue.
//!
//! This way, if a panic occurs while transing `expr`, the custom
//! cleanup scope C is pushed and hence the box will be freed. The trans
//! code for `expr` itself is responsible for freeing any other byproducts
//! that may be in play.

View File

@ -10,6 +10,25 @@
//! # Translation of Expressions
//!
//! The expr module handles translation of expressions. The most general
//! translation routine is `trans()`, which will translate an expression
//! into a datum. `trans_into()` is also available, which will translate
//! an expression and write the result directly into memory, sometimes
//! avoiding the need for a temporary stack slot. Finally,
//! `trans_to_lvalue()` is available if you'd like to ensure that the
//! result has cleanup scheduled.
//!
//! Internally, each of these functions dispatches to various other
//! expression functions depending on the kind of expression. We divide
//! up expressions into:
//!
//! - **Datum expressions:** Those that most naturally yield values.
//! Examples would be `22`, `box x`, or `a + b` (when not overloaded).
//! - **DPS expressions:** Those that most naturally write into a location
//! in memory. Examples would be `foo()` or `Point { x: 3, y: 4 }`.
//! - **Statement expressions:** That that do not generate a meaningful
//! result. Examples would be `while { ... }` or `return 44`.
//!
//! Public entry points:
//!
//! - `trans_into(bcx, expr, dest) -> bcx`: evaluates an expression,
@ -26,8 +45,6 @@
//! creating a temporary stack slot if necessary.
//!
//! - `trans_local_var -> Datum`: looks up a local variable or upvar.
//!
//! See doc.rs for more comments.
#![allow(non_camel_case_types)]

View File

@ -19,7 +19,6 @@ pub use self::common::gensym_name;
#[macro_use]
mod macros;
mod doc;
mod inline;
mod monomorphize;
mod controlflow;

View File

@ -0,0 +1,111 @@
# Method lookup
Method lookup can be rather complex due to the interaction of a number
of factors, such as self types, autoderef, trait lookup, etc. This
file provides an overview of the process. More detailed notes are in
the code itself, naturally.
One way to think of method lookup is that we convert an expression of
the form:
receiver.method(...)
into a more explicit UFCS form:
Trait::method(ADJ(receiver), ...) // for a trait call
ReceiverType::method(ADJ(receiver), ...) // for an inherent method call
Here `ADJ` is some kind of adjustment, which is typically a series of
autoderefs and then possibly an autoref (e.g., `&**receiver`). However
we sometimes do other adjustments and coercions along the way, in
particular unsizing (e.g., converting from `[T, ..n]` to `[T]`).
## The Two Phases
Method lookup is divided into two major phases: probing (`probe.rs`)
and confirmation (`confirm.rs`). The probe phase is when we decide
what method to call and how to adjust the receiver. The confirmation
phase "applies" this selection, updating the side-tables, unifying
type variables, and otherwise doing side-effectful things.
One reason for this division is to be more amenable to caching. The
probe phase produces a "pick" (`probe::Pick`), which is designed to be
cacheable across method-call sites. Therefore, it does not include
inference variables or other information.
## Probe phase
The probe phase (`probe.rs`) decides what method is being called and
how to adjust the receiver.
### Steps
The first thing that the probe phase does is to create a series of
*steps*. This is done by progressively dereferencing the receiver type
until it cannot be deref'd anymore, as well as applying an optional
"unsize" step. So if the receiver has type `Rc<Box<[T; 3]>>`, this
might yield:
Rc<Box<[T; 3]>>
Box<[T; 3]>
[T; 3]
[T]
### Candidate assembly
We then search along those steps to create a list of *candidates*. A
`Candidate` is a method item that might plausibly be the method being
invoked. For each candidate, we'll derive a "transformed self type"
that takes into account explicit self.
Candidates are grouped into two kinds, inherent and extension.
**Inherent candidates** are those that are derived from the
type of the receiver itself. So, if you have a receiver of some
nominal type `Foo` (e.g., a struct), any methods defined within an
impl like `impl Foo` are inherent methods. Nothing needs to be
imported to use an inherent method, they are associated with the type
itself (note that inherent impls can only be defined in the same
module as the type itself).
FIXME: Inherent candidates are not always derived from impls. If you
have a trait object, such as a value of type `Box<ToString>`, then the
trait methods (`to_string()`, in this case) are inherently associated
with it. Another case is type parameters, in which case the methods of
their bounds are inherent. However, this part of the rules is subject
to change: when DST's "impl Trait for Trait" is complete, trait object
dispatch could be subsumed into trait matching, and the type parameter
behavior should be reconsidered in light of where clauses.
**Extension candidates** are derived from imported traits. If I have
the trait `ToString` imported, and I call `to_string()` on a value of
type `T`, then we will go off to find out whether there is an impl of
`ToString` for `T`. These kinds of method calls are called "extension
methods". They can be defined in any module, not only the one that
defined `T`. Furthermore, you must import the trait to call such a
method.
So, let's continue our example. Imagine that we were calling a method
`foo` with the receiver `Rc<Box<[T; 3]>>` and there is a trait `Foo`
that defines it with `&self` for the type `Rc<U>` as well as a method
on the type `Box` that defines `Foo` but with `&mut self`. Then we
might have two candidates:
&Rc<Box<[T; 3]>> from the impl of `Foo` for `Rc<U>` where `U=Box<T; 3]>
&mut Box<[T; 3]>> from the inherent impl on `Box<U>` where `U=[T; 3]`
### Candidate search
Finally, to actually pick the method, we will search down the steps,
trying to match the receiver type against the candidate types. At
each step, we also consider an auto-ref and auto-mut-ref to see whether
that makes any of the candidates match. We pick the first step where
we find a match.
In the case of our example, the first step is `Rc<Box<[T; 3]>>`,
which does not itself match any candidate. But when we autoref it, we
get the type `&Rc<Box<[T; 3]>>` which does match. We would then
recursively consider all where-clauses that appear on the impl: if
those match (or we cannot rule out that they do), then this is the
method we would pick. Otherwise, we would continue down the series of
steps.

View File

@ -1,121 +0,0 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! # Method lookup
//!
//! Method lookup can be rather complex due to the interaction of a number
//! of factors, such as self types, autoderef, trait lookup, etc. This
//! file provides an overview of the process. More detailed notes are in
//! the code itself, naturally.
//!
//! One way to think of method lookup is that we convert an expression of
//! the form:
//!
//! receiver.method(...)
//!
//! into a more explicit UFCS form:
//!
//! Trait::method(ADJ(receiver), ...) // for a trait call
//! ReceiverType::method(ADJ(receiver), ...) // for an inherent method call
//!
//! Here `ADJ` is some kind of adjustment, which is typically a series of
//! autoderefs and then possibly an autoref (e.g., `&**receiver`). However
//! we sometimes do other adjustments and coercions along the way, in
//! particular unsizing (e.g., converting from `[T, ..n]` to `[T]`).
//!
//! ## The Two Phases
//!
//! Method lookup is divided into two major phases: probing (`probe.rs`)
//! and confirmation (`confirm.rs`). The probe phase is when we decide
//! what method to call and how to adjust the receiver. The confirmation
//! phase "applies" this selection, updating the side-tables, unifying
//! type variables, and otherwise doing side-effectful things.
//!
//! One reason for this division is to be more amenable to caching. The
//! probe phase produces a "pick" (`probe::Pick`), which is designed to be
//! cacheable across method-call sites. Therefore, it does not include
//! inference variables or other information.
//!
//! ## Probe phase
//!
//! The probe phase (`probe.rs`) decides what method is being called and
//! how to adjust the receiver.
//!
//! ### Steps
//!
//! The first thing that the probe phase does is to create a series of
//! *steps*. This is done by progressively dereferencing the receiver type
//! until it cannot be deref'd anymore, as well as applying an optional
//! "unsize" step. So if the receiver has type `Rc<Box<[T; 3]>>`, this
//! might yield:
//!
//! Rc<Box<[T; 3]>>
//! Box<[T; 3]>
//! [T; 3]
//! [T]
//!
//! ### Candidate assembly
//!
//! We then search along those steps to create a list of *candidates*. A
//! `Candidate` is a method item that might plausibly be the method being
//! invoked. For each candidate, we'll derive a "transformed self type"
//! that takes into account explicit self.
//!
//! Candidates are grouped into two kinds, inherent and extension.
//!
//! **Inherent candidates** are those that are derived from the
//! type of the receiver itself. So, if you have a receiver of some
//! nominal type `Foo` (e.g., a struct), any methods defined within an
//! impl like `impl Foo` are inherent methods. Nothing needs to be
//! imported to use an inherent method, they are associated with the type
//! itself (note that inherent impls can only be defined in the same
//! module as the type itself).
//!
//! FIXME: Inherent candidates are not always derived from impls. If you
//! have a trait object, such as a value of type `Box<ToString>`, then the
//! trait methods (`to_string()`, in this case) are inherently associated
//! with it. Another case is type parameters, in which case the methods of
//! their bounds are inherent. However, this part of the rules is subject
//! to change: when DST's "impl Trait for Trait" is complete, trait object
//! dispatch could be subsumed into trait matching, and the type parameter
//! behavior should be reconsidered in light of where clauses.
//!
//! **Extension candidates** are derived from imported traits. If I have
//! the trait `ToString` imported, and I call `to_string()` on a value of
//! type `T`, then we will go off to find out whether there is an impl of
//! `ToString` for `T`. These kinds of method calls are called "extension
//! methods". They can be defined in any module, not only the one that
//! defined `T`. Furthermore, you must import the trait to call such a
//! method.
//!
//! So, let's continue our example. Imagine that we were calling a method
//! `foo` with the receiver `Rc<Box<[T; 3]>>` and there is a trait `Foo`
//! that defines it with `&self` for the type `Rc<U>` as well as a method
//! on the type `Box` that defines `Foo` but with `&mut self`. Then we
//! might have two candidates:
//!
//! &Rc<Box<[T; 3]>> from the impl of `Foo` for `Rc<U>` where `U=Box<T; 3]>
//! &mut Box<[T; 3]>> from the inherent impl on `Box<U>` where `U=[T; 3]`
//!
//! ### Candidate search
//!
//! Finally, to actually pick the method, we will search down the steps,
//! trying to match the receiver type against the candidate types. At
//! each step, we also consider an auto-ref and auto-mut-ref to see whether
//! that makes any of the candidates match. We pick the first step where
//! we find a match.
//!
//! In the case of our example, the first step is `Rc<Box<[T; 3]>>`,
//! which does not itself match any candidate. But when we autoref it, we
//! get the type `&Rc<Box<[T; 3]>>` which does match. We would then
//! recursively consider all where-clauses that appear on the impl: if
//! those match (or we cannot rule out that they do), then this is the
//! method we would pick. Otherwise, we would continue down the series of
//! steps.

View File

@ -32,7 +32,6 @@ pub use self::CandidateSource::*;
pub use self::suggest::{report_error, AllTraitsVec};
mod confirm;
mod doc;
mod probe;
mod suggest;