parent
f55e5dfaa5
commit
839725a35d
|
|
@ -1,25 +1,28 @@
|
||||||
# Inference of opaque types (type alias `impl Trait`)
|
# Inference of opaque types (`impl Trait`)
|
||||||
|
|
||||||
This page describes how the compiler infers the hidden type for an opaque type.
|
This page describes how the compiler infers the [hidden type] for an [opaque type].
|
||||||
This kind of type inference is particularly complex because,
|
This kind of type inference is particularly complex because,
|
||||||
unlike other kinds of type inference,
|
unlike other kinds of type inference,
|
||||||
it works across functions and function bodies.
|
it can work across functions and function bodies.
|
||||||
|
|
||||||
|
[hidden type]: https://rustc-dev-guide.rust-lang.org/borrow_check/region_inference/member_constraints.html?highlight=%22hidden%20type%22#member-constraints
|
||||||
|
[opaque type]: https://rustc-dev-guide.rust-lang.org/opaque-types-type-alias-impl-trait.html
|
||||||
|
|
||||||
## Running example
|
## Running example
|
||||||
|
|
||||||
To help explain how it works, let's start with a simple example.
|
To help explain how it works, let's consider an example.
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
mod m {
|
mod m {
|
||||||
pub type Seq<T> = impl IntoIterator<Item = T>;
|
pub type Seq<T> = impl IntoIterator<Item = T>;
|
||||||
|
|
||||||
pub fn produce_singleton<T>(t: T) -> Seq<T> {
|
pub fn produce_singleton<T>(t: T) -> Seq<T> {
|
||||||
vec![t]
|
vec![t]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn produce_doubleton<T>(t: T, u: T) -> Seq<T> {
|
pub fn produce_doubleton<T>(t: T, u: T) -> Seq<T> {
|
||||||
vec![t, u]
|
vec![t, u]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +31,7 @@ fn is_send<T: Send>(_: &T) {}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let elems = m::produce_singleton(22);
|
let elems = m::produce_singleton(22);
|
||||||
|
|
||||||
is_send(&elems);
|
is_send(&elems);
|
||||||
|
|
||||||
for elem in elems {
|
for elem in elems {
|
||||||
|
|
@ -37,53 +40,68 @@ pub fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
* In this example, the opaque type is `Seq<T>`:
|
In this code, the *opaque type* is `Seq<T>`.
|
||||||
* Its defining scope is the module `m`.
|
Its defining scope is the module `m`.
|
||||||
* Its hidden type is `Vec<T>`, which is inferred from:
|
Its *hidden type* is `Vec<T>`,
|
||||||
* `m::produce_singleton`
|
which is inferred from `m::produce_singleton` and `m::produce_doubleton`.
|
||||||
* `m::produce_doubleton`
|
|
||||||
* In the `main` function, the opaque type is out of scope:
|
In the `main` function, the opaque type is out of its defining scope.
|
||||||
* When `main` calls `m::produce_singleton`,
|
When `main` calls `m::produce_singleton`, it gets back a reference to the opaque type `Seq<i32>`.
|
||||||
it gets back a reference to the opaque type `Seq<i32>`.
|
The `is_send` call checks that `Seq<i32>: Send`.
|
||||||
* The `is_send` call checks that `Seq<i32>:
|
`Send` is not listed amongst the bounds of the impl trait,
|
||||||
Send`. `Send` is not listed amongst the bounds of the impl trait,
|
but because of auto-trait leakage, we are able to infer that it holds.
|
||||||
but because of auto-trait leakage,
|
The `for` loop desugaring requires that `Seq<T>: IntoIterator`,
|
||||||
we are able to infer that it holds.
|
which is provable from the bounds declared on `Seq<T>`.
|
||||||
* The for loop desugaring requires that `Seq<T>: IntoIterator`,
|
|
||||||
which is provable from the bounds declared on `Seq<T>`.
|
|
||||||
|
|
||||||
### Type-checking `main`
|
### Type-checking `main`
|
||||||
|
|
||||||
Let's start by looking what happens when we type-check `main`. Initially we invoke `produce_singleton` and the return type is an opaque type [`OpaqueTy`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/enum.ItemKind.html#variant.OpaqueTy).
|
Let's start by looking what happens when we type-check `main`.
|
||||||
|
Initially we invoke `produce_singleton` and the return type is an opaque type
|
||||||
|
[`OpaqueTy`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/enum.ItemKind.html#variant.OpaqueTy).
|
||||||
|
|
||||||
#### Type-checking the for loop
|
#### Type-checking the for loop
|
||||||
|
|
||||||
* Explain how the for loop works:
|
The for loop desugars the `in elems` part to `IntoIterator::into_iter(elems)`.
|
||||||
* We look at the bounds, we are able to type check it as is
|
`elems` is of type `Seq<T>`, so the type checker registers a `Seq<T>: IntoIterator` obligation.
|
||||||
|
This obligation is trivially satisfied,
|
||||||
|
because `Seq<T>` is an opaque type (`impl IntoIterator<Item = T>`) that has a bound for the trait.
|
||||||
|
Similar to how a `U: Foo` where bound allows `U` to trivially satisfy `Foo`,
|
||||||
|
opaque types' bounds are available to the type checker and are used to fulfill obligations.
|
||||||
|
|
||||||
|
The type of `elem` in the for loop is inferred to be `<Seq<T> as IntoIterator>::Item`, which is `T`.
|
||||||
|
At no point is the type checker interested in the hidden type.
|
||||||
|
|
||||||
#### Type-checking the `is_send` call
|
#### Type-checking the `is_send` call
|
||||||
|
|
||||||
* Explain how it invokes `type_of`
|
When trying to prove auto trait bounds,
|
||||||
* We look at the bounds, we are able to type check it as is
|
we first repeat the process as above,
|
||||||
|
to see if the auto trait is in the bound list of the opaque type.
|
||||||
|
If that fails, we reveal the hidden type of the opaque type,
|
||||||
|
but only to prove this specific trait bound, not in general.
|
||||||
|
Revealing is done by invoking the `type_of` query on the `DefId` of the opaque type.
|
||||||
|
The query will internally request the hidden types from the defining function(s)
|
||||||
|
and return that (see [the section on `type_of`](#Within-the-type_of-query) for more details).
|
||||||
|
|
||||||
|
#### Flowchart of type checking steps
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
TypeChecking["type checking `main`"]
|
TypeChecking["type checking `main`"]
|
||||||
subgraph TypeOfSeq["type_of(Seq<T>) query"]
|
subgraph TypeOfSeq["type_of(Seq<T>) query"]
|
||||||
WalkModuleHir["Walk the HIR for the module `m`\nto find the hidden types from each\nfunction within"]
|
WalkModuleHir["Walk the HIR for the module `m`\nto find the hidden types from each\nfunction/const/static within"]
|
||||||
VisitProduceSingleton["visit `produce_singleton`"]
|
VisitProduceSingleton["visit `produce_singleton`"]
|
||||||
InterimType["`produce_singleton` hidden type is `Vec<T>`\nkeep searching"]
|
InterimType["`produce_singleton` hidden type is `Vec<T>`\nkeep searching"]
|
||||||
VisitProduceDoubleton["visit `produce_doubleton`"]
|
VisitProduceDoubleton["visit `produce_doubleton`"]
|
||||||
CompareType["`produce_doubleton` hidden type is also Vec<T>\nthis matches what we saw before ✅"]
|
CompareType["`produce_doubleton` hidden type is also Vec<T>\nthis matches what we saw before ✅"]
|
||||||
Done["Return `Vec<T>`"]
|
Done["No more items to look at in scope\nReturn `Vec<T>`"]
|
||||||
end
|
end
|
||||||
|
|
||||||
BorrowCheckProduceSingleton["`borrow_check(produce_singleton)`"]
|
BorrowCheckProduceSingleton["`borrow_check(produce_singleton)`"]
|
||||||
TypeCheckProduceSingleton["`type_check(produce_singleton)`"]
|
TypeCheckProduceSingleton["`type_check(produce_singleton)`"]
|
||||||
|
|
||||||
BorrowCheckProduceDoubleton["`borrow_check(produce_doubleton)`"]
|
BorrowCheckProduceDoubleton["`borrow_check(produce_doubleton)`"]
|
||||||
TypeCheckProduceDoubleton["`type_check(produce_doubleton)`"]
|
TypeCheckProduceDoubleton["`type_check(produce_doubleton)`"]
|
||||||
|
|
||||||
Substitute["Substitute `T => u32`,\nyielding `Vec<i32>` as the hidden type"]
|
Substitute["Substitute `T => u32`,\nyielding `Vec<i32>` as the hidden type"]
|
||||||
CheckSend["Check that `Vec<i32>: Send` ✅"]
|
CheckSend["Check that `Vec<i32>: Send` ✅"]
|
||||||
|
|
||||||
|
|
@ -97,14 +115,14 @@ flowchart TD
|
||||||
VisitProduceDoubleton --> BorrowCheckProduceDoubleton
|
VisitProduceDoubleton --> BorrowCheckProduceDoubleton
|
||||||
BorrowCheckProduceDoubleton --> TypeCheckProduceDoubleton
|
BorrowCheckProduceDoubleton --> TypeCheckProduceDoubleton
|
||||||
TypeCheckProduceDoubleton --> CompareType --> Done
|
TypeCheckProduceDoubleton --> CompareType --> Done
|
||||||
Done --> Substitute --> CheckSend
|
Done --> Substitute --> CheckSend
|
||||||
```
|
```
|
||||||
|
|
||||||
### Within the `type_of` query
|
### Within the `type_of` query
|
||||||
|
|
||||||
The `type_of` query, when applied to an opaque type O, returns the hidden type.
|
The `type_of` query, when applied to an opaque type O, returns the hidden type.
|
||||||
That hidden type is computed by combining the results from each constraining function
|
That hidden type is computed by combining the results
|
||||||
within defining scope of O.
|
from each constraining function within the defining scope of O.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
|
|
@ -124,12 +142,18 @@ flowchart TD
|
||||||
ReportError["Report an error"]
|
ReportError["Report an error"]
|
||||||
ReportError --> Complete["Item I complete"]
|
ReportError --> Complete["Item I complete"]
|
||||||
Complete --> Iterate
|
Complete --> Iterate
|
||||||
|
|
||||||
FindOpaqueTyConstraints -- All constraints found --> Done
|
FindOpaqueTyConstraints -- All constraints found --> Done
|
||||||
Done["Done"]
|
Done["Done"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Within the ordinary type check of a single function
|
### Relating an opaque type to another type
|
||||||
|
|
||||||
|
There is one central place where an opaqe type gets its hidden type constrained,
|
||||||
|
and that is the `handle_opaque_type` function.
|
||||||
|
Amusingly it takes two types, so you can pass any two types,
|
||||||
|
but one of them should be an opaque type.
|
||||||
|
The order is only important for diagnostics.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
|
|
@ -138,78 +162,118 @@ flowchart TD
|
||||||
sub.rs
|
sub.rs
|
||||||
lub.rs
|
lub.rs
|
||||||
end
|
end
|
||||||
|
|
||||||
typecheck -- infcx.opaque_ty_obligation --> Enqueue["Enqueue P of kind PredicateKind::OpaqueType"]
|
typecheck --> TwoSimul
|
||||||
|
|
||||||
Enqueue -- ... some time later ... --> Fulfill
|
|
||||||
|
|
||||||
Fulfill["Fulfillment context dequeues P "]
|
|
||||||
|
|
||||||
subgraph handleopaquetype["infcx.handle_opaque_type"]
|
subgraph handleopaquetype["infcx.handle_opaque_type"]
|
||||||
Handle["Check anchor to determine if we are in a query"]
|
|
||||||
|
|
||||||
Handle -- Have anchor, not query --> TwoSimul
|
|
||||||
Handle -- No anchor, in query --> Query
|
|
||||||
|
|
||||||
Query["See query section below"]
|
|
||||||
|
|
||||||
TwoSimul["Defining two opaque types simultaneously?"]
|
TwoSimul["Defining two opaque types simultaneously?"]
|
||||||
|
|
||||||
TwoSimul -- Yes --> ReportError["Report error"]
|
TwoSimul -- Yes --> ReportError["Report error"]
|
||||||
|
|
||||||
TwoSimul -- No --> AlreadyHasValue
|
TwoSimul -- No --> MayDefine -- Yes --> RegisterOpaqueType --> AlreadyHasValue
|
||||||
|
|
||||||
|
MayDefine -- No --> ReportError
|
||||||
|
|
||||||
|
MayDefine["In defining scope OR in query?"]
|
||||||
|
|
||||||
AlreadyHasValue["Opaque type X already has\na registered value?"]
|
AlreadyHasValue["Opaque type X already has\na registered value?"]
|
||||||
|
|
||||||
AlreadyHasValue -- No --> RegisterOpaqueType["Register opaque type with\nother type as value"]
|
AlreadyHasValue -- No --> Obligations["Register opaque type bounds\nas obligations for hidden type"]
|
||||||
|
|
||||||
AlreadyHasValue -- Yes --> EquateOpaqueTypes["Equate new hidden type with old hidden type"]
|
RegisterOpaqueType["Register opaque type with\nother type as value"]
|
||||||
|
|
||||||
|
AlreadyHasValue -- Yes --> EquateOpaqueTypes["Equate new hidden type\nwith old hidden type"]
|
||||||
end
|
end
|
||||||
|
|
||||||
Fulfill --> Handle
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Interactions with queries
|
### Interactions with queries
|
||||||
|
|
||||||
When queries encounter a `opaque_ty_obligation`,
|
When queries handle opaque types,
|
||||||
they do not try to process them,
|
they cannot figure out whether they are in a defining scope,
|
||||||
but instead just store the constraints into the infcx.
|
so they just assume they are.
|
||||||
|
|
||||||
```mermaid
|
The registered hidden types are stored into the `QueryResponse` struct
|
||||||
graph TD
|
in the `opaque_types` field (the function
|
||||||
subgraph handleopaquetype["infcx.handle_opaque_type"]
|
`take_opaque_types_for_query_response` reads them out).
|
||||||
Handle["Check anchor to determine if we are in a query"]
|
|
||||||
Handle -- Have anchor, not query --> NotQuery
|
|
||||||
Handle -- No anchor, in query --> HavePrevious
|
|
||||||
|
|
||||||
NotQuery["See 'not query' case above"]
|
|
||||||
HavePrevious["Have previous hidden type?"]
|
|
||||||
|
|
||||||
HavePrevious -- Yes --> Unify
|
|
||||||
HavePrevious -- No --> Install
|
|
||||||
|
|
||||||
Unify["Equate new and previous hidden types"]
|
|
||||||
Install["Install hidden type into table"]
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
The registered hidden types are stored into the `QueryResponse` struct in
|
|
||||||
the `opaque_types` field
|
|
||||||
(the function `take_opaque_types_for_query_response` reads them out).
|
|
||||||
|
|
||||||
When the `QueryResponse` is instantiated into the surrounding infcx in
|
When the `QueryResponse` is instantiated into the surrounding infcx in
|
||||||
`query_response_substitution_guess`,
|
`query_response_substitution_guess`,
|
||||||
we convert each hidden type constraint by invoking `handle_opaque_type` (as above).
|
we convert each hidden type constraint by invoking `handle_opaque_type` (as above).
|
||||||
|
|
||||||
There is one bit of "weirdness".
|
There is one bit of "weirdness".
|
||||||
The instantiated opaque types are stored in a *map*,
|
The instantiated opaque types have an order
|
||||||
and we have to pick one opaque type to use as the key of that map.
|
(if one opaque type was compared with another,
|
||||||
|
and we have to pick one opaque type to use as the one that gets its hidden type assigned).
|
||||||
We use the one that is considered "expected".
|
We use the one that is considered "expected".
|
||||||
But really both of the opaque types may have defining uses.
|
But really both of the opaque types may have defining uses.
|
||||||
When the query result is instantiated,
|
When the query result is instantiated,
|
||||||
that will be re-evaluated from the context that is using the query.
|
that will be re-evaluated from the context that is using the query.
|
||||||
|
The final context (typeck of a function, mir borrowck or wf-checks)
|
||||||
|
will know which opaque type can actually be instantiated
|
||||||
|
and then handle it correctly.
|
||||||
|
|
||||||
### Within the MIR borrow checker
|
### Within the MIR borrow checker
|
||||||
|
|
||||||
The MIR borrow checker relates things via `nll_relate`...
|
The MIR borrow checker relates things via `nll_relate` and only cares about regions.
|
||||||
|
Any type relation will trigger the binding of hidden types,
|
||||||
|
so the borrow checker is doing the same thing as the type checker,
|
||||||
|
but ignores obivously dead code (e.g. after a panic).
|
||||||
|
The borrow checker is also the source of truth when it comes to hidden types,
|
||||||
|
as it is the only one who can properly figure out what lifetimes on the hidden type correspond
|
||||||
|
to which lifetimes on the opaque type declaration.
|
||||||
|
|
||||||
|
## Backwards compatibility hacks
|
||||||
|
|
||||||
|
`impl Trait` in return position has various quirks that were not part
|
||||||
|
of any RFCs and are likely accidental stabilizations.
|
||||||
|
To support these,
|
||||||
|
the `replace_opaque_types_with_inference_vars` is being used to reintroduce the previous behaviour.
|
||||||
|
|
||||||
|
There are three backwards compatibility hacks:
|
||||||
|
|
||||||
|
1. All return sites share the same inference variable,
|
||||||
|
so some return sites may only compile if another return site uses a concrete type.
|
||||||
|
```rust
|
||||||
|
fn foo() -> impl Debug {
|
||||||
|
if false {
|
||||||
|
return std::iter::empty().collect();
|
||||||
|
}
|
||||||
|
vec![42]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. Associated type equality constraints for `impl Trait` can be used
|
||||||
|
as long as the hidden type satisfies the trait bounds on the associated type.
|
||||||
|
The opaque `impl Trait` signature does not need to satisfy them.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
trait Duh {}
|
||||||
|
|
||||||
|
impl Duh for i32 {}
|
||||||
|
|
||||||
|
trait Trait {
|
||||||
|
type Assoc: Duh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the fact that `R` is the `::Output` projection on `F` causes
|
||||||
|
// an intermediate inference var to be generated which is then later
|
||||||
|
// compared against the actually found `Assoc` type.
|
||||||
|
impl<R: Duh, F: FnMut() -> R> Trait for F {
|
||||||
|
type Assoc = R;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The `impl Send` here is then later compared against the inference var
|
||||||
|
// created, causing the inference var to be set to `impl Send` instead of
|
||||||
|
// the hidden type. We already have obligations registered on the inference
|
||||||
|
// var to make it uphold the `: Duh` bound on `Trait::Assoc`. The opaque
|
||||||
|
// type does not implement `Duh`, even if its hidden type does.
|
||||||
|
// Lazy TAIT would error out, but we inserted a hack to make it work again,
|
||||||
|
// keeping backwards compatibility.
|
||||||
|
fn foo() -> impl Trait<Assoc = impl Send> {
|
||||||
|
|| 42
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. Closures cannot create hidden types for their parent function's `impl Trait`.
|
||||||
|
This point is mostly moot,
|
||||||
|
because of point 1 introducing inference vars,
|
||||||
|
so the closure only ever sees the inference var, but should we fix 1, this will become a problem.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue