Polish lowering chapters and update rules
This commit is contained in:
parent
70b91d54a5
commit
cd3afd5ed6
|
|
@ -67,7 +67,7 @@ type.)
|
||||||
We could apply that rule to normalize either of the examples that
|
We could apply that rule to normalize either of the examples that
|
||||||
we've seen so far.
|
we've seen so far.
|
||||||
|
|
||||||
## Skolemized associated types
|
## Placeholder associated types
|
||||||
|
|
||||||
Sometimes however we want to work with associated types that cannot be
|
Sometimes however we want to work with associated types that cannot be
|
||||||
normalized. For example, consider this function:
|
normalized. For example, consider this function:
|
||||||
|
|
@ -78,20 +78,29 @@ fn foo<T: IntoIterator>(...) { ... }
|
||||||
|
|
||||||
In this context, how would we normalize the type `T::Item`? Without
|
In this context, how would we normalize the type `T::Item`? Without
|
||||||
knowing what `T` is, we can't really do so. To represent this case, we
|
knowing what `T` is, we can't really do so. To represent this case, we
|
||||||
introduce a type called a **skolemized associated type
|
introduce a type called a **placeholder associated type
|
||||||
projection**. This is written like so `(IntoIterator::Item)<T>`. You
|
projection**. This is written like so `(IntoIterator::Item)<T>`. You
|
||||||
may note that it looks a lot like a regular type (e.g., `Option<T>`),
|
may note that it looks a lot like a regular type (e.g., `Option<T>`),
|
||||||
except that the "name" of the type is `(IntoIterator::Item)`. This is
|
except that the "name" of the type is `(IntoIterator::Item)`. This is
|
||||||
not an accident: skolemized associated type projections work just like
|
not an accident: placeholder associated type projections work just like
|
||||||
ordinary types like `Vec<T>` when it comes to unification. That is,
|
ordinary types like `Vec<T>` when it comes to unification. That is,
|
||||||
they are only considered equal if (a) they are both references to the
|
they are only considered equal if (a) they are both references to the
|
||||||
same associated type, like `IntoIterator::Item` and (b) their type
|
same associated type, like `IntoIterator::Item` and (b) their type
|
||||||
arguments are equal.
|
arguments are equal.
|
||||||
|
|
||||||
Skolemized associated types are never written directly by the user.
|
Placeholder associated types are never written directly by the user.
|
||||||
They are used internally by the trait system only, as we will see
|
They are used internally by the trait system only, as we will see
|
||||||
shortly.
|
shortly.
|
||||||
|
|
||||||
|
In rustc, they correspond to the `TyKind::UnnormalizedProjectionTy` enum
|
||||||
|
variant, declared in [`librustc/ty/sty.rs`][sty]. In chalk, we use an
|
||||||
|
`ApplicationTy` with a name living in a special namespace dedicated to
|
||||||
|
placeholder associated types (see the `TypeName` enum declared in
|
||||||
|
[`chalk-ir/src/lib.rs`][chalk_type_name]).
|
||||||
|
|
||||||
|
[sty]: https://github.com/rust-lang/rust/blob/master/src/librustc/ty/sty.rs
|
||||||
|
[chalk_type_name]: https://github.com/rust-lang-nursery/chalk/blob/master/chalk-ir/src/lib.rs
|
||||||
|
|
||||||
## Projection equality
|
## Projection equality
|
||||||
|
|
||||||
So far we have seen two ways to answer the question of "When can we
|
So far we have seen two ways to answer the question of "When can we
|
||||||
|
|
@ -99,7 +108,7 @@ consider an associated type projection equal to another type?":
|
||||||
|
|
||||||
- the `Normalize` predicate could be used to transform associated type
|
- the `Normalize` predicate could be used to transform associated type
|
||||||
projections when we knew which impl was applicable;
|
projections when we knew which impl was applicable;
|
||||||
- **skolemized** associated types can be used when we don't.
|
- **placeholder** associated types can be used when we don't.
|
||||||
|
|
||||||
We now introduce the `ProjectionEq` predicate to bring those two cases
|
We now introduce the `ProjectionEq` predicate to bring those two cases
|
||||||
together. The `ProjectionEq` predicate looks like so:
|
together. The `ProjectionEq` predicate looks like so:
|
||||||
|
|
@ -109,7 +118,7 @@ ProjectionEq(<T as IntoIterator>::Item = U)
|
||||||
```
|
```
|
||||||
|
|
||||||
and we will see that it can be proven *either* via normalization or
|
and we will see that it can be proven *either* via normalization or
|
||||||
skolemization. As part of lowering an associated type declaration from
|
via the placeholder type. As part of lowering an associated type declaration from
|
||||||
some trait, we create two program clauses for `ProjectionEq`:
|
some trait, we create two program clauses for `ProjectionEq`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
|
||||||
|
|
@ -37,15 +37,33 @@ paper
|
||||||
["A Proof Procedure for the Logic of Hereditary Harrop Formulas"][pphhf]
|
["A Proof Procedure for the Logic of Hereditary Harrop Formulas"][pphhf]
|
||||||
gives the details.
|
gives the details.
|
||||||
|
|
||||||
|
In terms of code, these types are defined in
|
||||||
|
[`librustc/traits/mod.rs`][traits_mod] in rustc, and in
|
||||||
|
[`chalk-ir/src/lib.rs`][chalk_ir] in chalk.
|
||||||
|
|
||||||
[pphhf]: ./bibliography.html#pphhf
|
[pphhf]: ./bibliography.html#pphhf
|
||||||
|
[traits_mod]: https://github.com/rust-lang/rust/blob/master/src/librustc/traits/mod.rs
|
||||||
|
[chalk_ir]: https://github.com/rust-lang-nursery/chalk/blob/master/chalk-ir/src/lib.rs
|
||||||
|
|
||||||
<a name="domain-goals"></a>
|
<a name="domain-goals"></a>
|
||||||
|
|
||||||
## Domain goals
|
## Domain goals
|
||||||
|
|
||||||
|
*Domain goals* are the atoms of the trait logic. As can be seen in the
|
||||||
|
definitions given above, general goals basically consist in a combination of
|
||||||
|
domain goals.
|
||||||
|
|
||||||
|
Moreover, flattenning a bit the definition of clauses given previously, one can
|
||||||
|
see that clauses are always of the form:
|
||||||
|
```text
|
||||||
|
forall<K1, ..., Kn> { DomainGoal :- Goal }
|
||||||
|
```
|
||||||
|
hence domain goals are in fact clauses LHS. That is, at the most granular level,
|
||||||
|
domain goals are what the trait solver will end up trying to prove.
|
||||||
|
|
||||||
<a name="trait-ref"></a>
|
<a name="trait-ref"></a>
|
||||||
|
|
||||||
To define the set of *domain goals* in our system, we need to first
|
To define the set of domain goals in our system, we need to first
|
||||||
introduce a few simple formulations. A **trait reference** consists of
|
introduce a few simple formulations. A **trait reference** consists of
|
||||||
the name of a trait along with a suitable set of inputs P0..Pn:
|
the name of a trait along with a suitable set of inputs P0..Pn:
|
||||||
|
|
||||||
|
|
@ -70,18 +88,24 @@ Projection = <P0 as TraitName<P1..Pn>>::AssocItem<Pn+1..Pm>
|
||||||
Given these, we can define a `DomainGoal` as follows:
|
Given these, we can define a `DomainGoal` as follows:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
DomainGoal = Implemented(TraitRef)
|
DomainGoal = Holds(WhereClause)
|
||||||
| ProjectionEq(Projection = Type)
|
|
||||||
| Normalize(Projection -> Type)
|
|
||||||
| FromEnv(TraitRef)
|
| FromEnv(TraitRef)
|
||||||
| FromEnv(Projection = Type)
|
| FromEnv(Type)
|
||||||
| WellFormed(Type)
|
|
||||||
| WellFormed(TraitRef)
|
| WellFormed(TraitRef)
|
||||||
| WellFormed(Projection = Type)
|
| WellFormed(Type)
|
||||||
|
| Normalize(Projection -> Type)
|
||||||
|
|
||||||
|
WhereClause = Implemented(TraitRef)
|
||||||
|
| ProjectionEq(Projection = Type)
|
||||||
| Outlives(Type: Region)
|
| Outlives(Type: Region)
|
||||||
| Outlives(Region: Region)
|
| Outlives(Region: Region)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`WhereClause` refers to a `where` clause that a Rust user would actually be able
|
||||||
|
to write in a Rust program. This abstraction exists only as a convenience as we
|
||||||
|
sometimes want to only coope with domain goals that are effectively writable in
|
||||||
|
Rust.
|
||||||
|
|
||||||
Let's break down each one of these, one-by-one.
|
Let's break down each one of these, one-by-one.
|
||||||
|
|
||||||
#### Implemented(TraitRef)
|
#### Implemented(TraitRef)
|
||||||
|
|
@ -109,12 +133,10 @@ also requires proving `Implemented(T: Trait)`.
|
||||||
[n]: ./associated-types.html#normalize
|
[n]: ./associated-types.html#normalize
|
||||||
[at]: ./associated-types.html
|
[at]: ./associated-types.html
|
||||||
|
|
||||||
#### FromEnv(TraitRef), FromEnv(Projection = Type)
|
#### FromEnv(TraitRef)
|
||||||
e.g. `FromEnv(Self: Add<i32>)`
|
e.g. `FromEnv(Self: Add<i32>)`
|
||||||
|
|
||||||
e.g. `FromEnv(<Self as StreamingIterator>::Item<'a> = &'a [u8])`
|
True if the inner `TraitRef` is *assumed* to be true,
|
||||||
|
|
||||||
True if the inner `TraitRef` or projection equality is *assumed* to be true;
|
|
||||||
that is, if it can be derived from the in-scope where clauses.
|
that is, if it can be derived from the in-scope where clauses.
|
||||||
|
|
||||||
For example, given the following function:
|
For example, given the following function:
|
||||||
|
|
@ -131,24 +153,50 @@ where clauses nest, so a function body inside an impl body inherits the
|
||||||
impl body's where clauses, too.
|
impl body's where clauses, too.
|
||||||
|
|
||||||
This and the next rule are used to implement [implied bounds]. As we'll see
|
This and the next rule are used to implement [implied bounds]. As we'll see
|
||||||
in the section on lowering, `FromEnv(X)` implies `Implemented(X)`, but not
|
in the section on lowering, `FromEnv(TraitRef)` implies `Implemented(TraitRef)`,
|
||||||
vice versa. This distinction is crucial to implied bounds.
|
but not vice versa. This distinction is crucial to implied bounds.
|
||||||
|
|
||||||
|
#### FromEnv(Type)
|
||||||
|
e.g. `FromEnv(HashSet<K>)`
|
||||||
|
|
||||||
|
True if the inner `Type` is *assumed* to be well-formed, that is, if it is an
|
||||||
|
input type of a function or an impl.
|
||||||
|
|
||||||
|
For example, given the following code:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
struct HashSet<K> where K: Hash { ... }
|
||||||
|
|
||||||
|
fn loud_insert<K>(set: &mut HashSet<K>, item: K) {
|
||||||
|
println!("inserting!");
|
||||||
|
set.insert(item);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`HashSet<K>` is an input type of the `loud_insert` function. Hence, we assume it
|
||||||
|
to be well-formed, so we would have `FromEnv(HashSet<K>)` inside the body or our
|
||||||
|
function. As we'll see in the section on lowering, `FromEnv(HashSet<K>)` implies
|
||||||
|
`Implemented(K: Hash)` because the
|
||||||
|
`HashSet` declaration was written with a `K: Hash` where clause. Hence, we don't
|
||||||
|
need to repeat that bound on the `loud_insert` function: we rather automatically
|
||||||
|
assume that it is true.
|
||||||
|
|
||||||
#### WellFormed(Item)
|
#### WellFormed(Item)
|
||||||
These goals imply that the given item is *well-formed*.
|
These goals imply that the given item is *well-formed*.
|
||||||
|
|
||||||
We can talk about different types of items being well-formed:
|
We can talk about different types of items being well-formed:
|
||||||
|
|
||||||
**Types**, like `WellFormed(Vec<i32>)`, which is true in Rust, or
|
* *Types*, like `WellFormed(Vec<i32>)`, which is true in Rust, or
|
||||||
`WellFormed(Vec<str>)`, which is not (because `str` is not `Sized`.)
|
`WellFormed(Vec<str>)`, which is not (because `str` is not `Sized`.)
|
||||||
|
|
||||||
**TraitRefs**, like `WellFormed(Vec<i32>: Clone)`.
|
* *TraitRefs*, like `WellFormed(Vec<i32>: Clone)`.
|
||||||
|
|
||||||
**Projections**, like `WellFormed(T: Iterator<Item = u32>)`.
|
|
||||||
|
|
||||||
Well-formedness is important to [implied bounds]. In particular, the reason
|
Well-formedness is important to [implied bounds]. In particular, the reason
|
||||||
it is okay to assume `FromEnv(T: Clone)` in the example above is that we
|
it is okay to assume `FromEnv(T: Clone)` in the `loud_clone` example is that we
|
||||||
_also_ verify `WellFormed(T: Clone)` for each call site of `loud_clone`.
|
_also_ verify `WellFormed(T: Clone)` for each call site of `loud_clone`.
|
||||||
|
Similarly, it is okay to assume `FromEnv(HashSet<K>)` in the `loud_insert`
|
||||||
|
example because we will verify `WellFormed(HashSet<K>)` for each call site of
|
||||||
|
`loud_insert`.
|
||||||
|
|
||||||
#### Outlives(Type: Region), Outlives(Region: Region)
|
#### Outlives(Type: Region), Outlives(Region: Region)
|
||||||
e.g. `Outlives(&'a str: 'b)`, `Outlives('a: 'static)`
|
e.g. `Outlives(&'a str: 'b)`, `Outlives('a: 'static)`
|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,17 @@ Trait solving is based around a few key ideas:
|
||||||
constraints can be checked by thet type checker.
|
constraints can be checked by thet type checker.
|
||||||
|
|
||||||
Note: this is not a complete list of topics. See the sidebar for more.
|
Note: this is not a complete list of topics. See the sidebar for more.
|
||||||
|
|
||||||
|
The design of the new-style trait solving currently happens in two places:
|
||||||
|
* The [chalk][chalk] repository is where we experiment with new ideas and
|
||||||
|
designs for the trait system. It basically consists of a unit testing framework
|
||||||
|
for the correctness and feasibility of the logical rules defining the new-style
|
||||||
|
trait system. It also provides the [`chalk_engine`][chalk_engine] crate, which
|
||||||
|
defines the new-style trait solver used both in the unit testing framework and
|
||||||
|
in rustc.
|
||||||
|
* Once we are happy with the logical rules, we proceed to implementing them in
|
||||||
|
rustc. This mainly happens in [`librustc_traits`][librustc_traits].
|
||||||
|
|
||||||
|
[chalk]: https://github.com/rust-lang-nursery/chalk
|
||||||
|
[chalk_engine]: https://github.com/rust-lang-nursery/chalk/tree/master/chalk-engine
|
||||||
|
[librustc_traits]: https://github.com/rust-lang/rust/tree/master/src/librustc_traits
|
||||||
|
|
|
||||||
|
|
@ -27,19 +27,24 @@ comment like so:
|
||||||
|
|
||||||
// Rule Foo-Bar-Baz
|
// Rule Foo-Bar-Baz
|
||||||
|
|
||||||
you can also search through the `librustc_traits` crate in rustc
|
The reference implementation of these rules is to be found in
|
||||||
to find the corresponding rules from the implementation.
|
[`chalk/src/rules.rs`][chalk_rules]. They are also ported in rustc in the
|
||||||
|
[`librustc_traits`][librustc_traits] crate.
|
||||||
|
|
||||||
|
[chalk_rules]: https://github.com/rust-lang-nursery/chalk/blob/master/src/rules.rs
|
||||||
|
[librustc_traits]: https://github.com/rust-lang/rust/tree/master/src/librustc_traits
|
||||||
|
|
||||||
## Lowering where clauses
|
## Lowering where clauses
|
||||||
|
|
||||||
When used in a goal position, where clauses can be mapped directly to
|
When used in a goal position, where clauses can be mapped directly to
|
||||||
[domain goals][dg], as follows:
|
the `Holds` variant of [domain goals][dg], as follows:
|
||||||
|
|
||||||
- `A0: Foo<A1..An>` maps to `Implemented(A0: Foo<A1..An>)`.
|
- `A0: Foo<A1..An>` maps to `Implemented(A0: Foo<A1..An>)`
|
||||||
- `A0: Foo<A1..An, Item = T>` maps to
|
|
||||||
`ProjectionEq(<A0 as Foo<A1..An>>::Item = T)`
|
|
||||||
- `T: 'r` maps to `Outlives(T, 'r)`
|
- `T: 'r` maps to `Outlives(T, 'r)`
|
||||||
- `'a: 'b` maps to `Outlives('a, 'b)`
|
- `'a: 'b` maps to `Outlives('a, 'b)`
|
||||||
|
- `A0: Foo<A1..An, Item = T>` is a bit special and expands to two distinct
|
||||||
|
goals, namely `Implemented(A0: Foo<A1..An>)` and
|
||||||
|
`ProjectionEq(<A0 as Foo<A1..An>>::Item = T)`
|
||||||
|
|
||||||
In the rules below, we will use `WC` to indicate where clauses that
|
In the rules below, we will use `WC` to indicate where clauses that
|
||||||
appear in Rust syntax; we will then use the same `WC` to indicate
|
appear in Rust syntax; we will then use the same `WC` to indicate
|
||||||
|
|
@ -54,11 +59,10 @@ on the lowered where clauses, as defined here:
|
||||||
|
|
||||||
- `FromEnv(WC)` – this indicates that:
|
- `FromEnv(WC)` – this indicates that:
|
||||||
- `Implemented(TraitRef)` becomes `FromEnv(TraitRef)`
|
- `Implemented(TraitRef)` becomes `FromEnv(TraitRef)`
|
||||||
- `ProjectionEq(Projection = Ty)` becomes `FromEnv(Projection = Ty)`
|
|
||||||
- other where-clauses are left intact
|
- other where-clauses are left intact
|
||||||
- `WellFormed(WC)` – this indicates that:
|
- `WellFormed(WC)` – this indicates that:
|
||||||
- `Implemented(TraitRef)` becomes `WellFormed(TraitRef)`
|
- `Implemented(TraitRef)` becomes `WellFormed(TraitRef)`
|
||||||
- `ProjectionEq(Projection = Ty)` becomes `WellFormed(Projection = Ty)`
|
- other where-clauses are left intact
|
||||||
|
|
||||||
*TODO*: I suspect that we want to alter the outlives relations too,
|
*TODO*: I suspect that we want to alter the outlives relations too,
|
||||||
but Chalk isn't modeling those right now.
|
but Chalk isn't modeling those right now.
|
||||||
|
|
@ -99,9 +103,11 @@ forall<Self, P1..Pn> {
|
||||||
#### Implied bounds
|
#### Implied bounds
|
||||||
|
|
||||||
The next few clauses have to do with implied bounds (see also
|
The next few clauses have to do with implied bounds (see also
|
||||||
[RFC 2089]). For each trait, we produce two clauses:
|
[RFC 2089] and the [implied bounds][implied_bounds] chapter for a more in depth
|
||||||
|
cover). For each trait, we produce two clauses:
|
||||||
|
|
||||||
[RFC 2089]: https://rust-lang.github.io/rfcs/2089-implied-bounds.html
|
[RFC 2089]: https://rust-lang.github.io/rfcs/2089-implied-bounds.html
|
||||||
|
[implied_bounds]: ./implied-bounds.md
|
||||||
|
|
||||||
```text
|
```text
|
||||||
// Rule Implied-Bound-From-Trait
|
// Rule Implied-Bound-From-Trait
|
||||||
|
|
@ -210,7 +216,7 @@ well-formed, we can also assume that its where clauses hold. That is,
|
||||||
we produce the following family of rules:
|
we produce the following family of rules:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
// Rule FromEnv-Type
|
// Rule Implied-Bound-From-Type
|
||||||
//
|
//
|
||||||
// For each where clause `WC`
|
// For each where clause `WC`
|
||||||
forall<P1..Pn> {
|
forall<P1..Pn> {
|
||||||
|
|
@ -280,10 +286,10 @@ forall<Self, P1..Pn, Pn+1..Pm, U> {
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text
|
||||||
// Rule ProjectionEq-Skolemize
|
// Rule ProjectionEq-Placeholder
|
||||||
//
|
//
|
||||||
// ProjectionEq can succeed by skolemizing, see "associated type"
|
// ProjectionEq can succeed through the placeholder associated type,
|
||||||
// chapter for more:
|
// see "associated type" chapter for more:
|
||||||
forall<Self, P1..Pn, Pn+1..Pm> {
|
forall<Self, P1..Pn, Pn+1..Pm> {
|
||||||
ProjectionEq(
|
ProjectionEq(
|
||||||
<Self as Trait<P1..Pn>>::AssocType<Pn+1..Pm> =
|
<Self as Trait<P1..Pn>>::AssocType<Pn+1..Pm> =
|
||||||
|
|
@ -303,7 +309,7 @@ elsewhere.
|
||||||
// For each `Bound` in `Bounds`:
|
// For each `Bound` in `Bounds`:
|
||||||
forall<Self, P1..Pn, Pn+1..Pm> {
|
forall<Self, P1..Pn, Pn+1..Pm> {
|
||||||
FromEnv(<Self as Trait<P1..Pn>>::AssocType<Pn+1..Pm>>: Bound) :-
|
FromEnv(<Self as Trait<P1..Pn>>::AssocType<Pn+1..Pm>>: Bound) :-
|
||||||
FromEnv(Self: Trait<P1..Pn>)
|
FromEnv(Self: Trait<P1..Pn>) && WC1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -314,7 +320,7 @@ type to be well-formed...
|
||||||
// Rule WellFormed-AssocTy
|
// Rule WellFormed-AssocTy
|
||||||
forall<Self, P1..Pn, Pn+1..Pm> {
|
forall<Self, P1..Pn, Pn+1..Pm> {
|
||||||
WellFormed((Trait::AssocType)<Self, P1..Pn, Pn+1..Pm>) :-
|
WellFormed((Trait::AssocType)<Self, P1..Pn, Pn+1..Pm>) :-
|
||||||
WC1, Implemented(Self: Trait<P1..Pn>)
|
Implemented(Self: Trait<P1..Pn>) && WC1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue