Add RPITIT documentation (#1764)
This commit is contained in:
parent
9e0902f9d6
commit
06205a1941
|
|
@ -131,6 +131,7 @@
|
||||||
- [Variance](./variance.md)
|
- [Variance](./variance.md)
|
||||||
- [Opaque Types](./opaque-types-type-alias-impl-trait.md)
|
- [Opaque Types](./opaque-types-type-alias-impl-trait.md)
|
||||||
- [Inference details](./opaque-types-impl-trait-inference.md)
|
- [Inference details](./opaque-types-impl-trait-inference.md)
|
||||||
|
- [Return Position Impl Trait In Trait](./return-position-impl-trait-in-trait.md)
|
||||||
- [Pattern and Exhaustiveness Checking](./pat-exhaustive-checking.md)
|
- [Pattern and Exhaustiveness Checking](./pat-exhaustive-checking.md)
|
||||||
- [MIR dataflow](./mir/dataflow.md)
|
- [MIR dataflow](./mir/dataflow.md)
|
||||||
- [Drop elaboration](./mir/drop-elaboration.md)
|
- [Drop elaboration](./mir/drop-elaboration.md)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,419 @@
|
||||||
|
# Return Position Impl Trait In Trait
|
||||||
|
|
||||||
|
Return-position impl trait in trait (RPITIT) is conceptually (and as of
|
||||||
|
[#112988], literally) sugar that turns RPITs in trait methods into
|
||||||
|
generic associated types (GATs) without the user having to define that
|
||||||
|
GAT either on the trait side or impl side.
|
||||||
|
|
||||||
|
RPITIT was originally implemented in [#101224], which added support for
|
||||||
|
async fn in trait (AFIT), since the implementation for RPITIT came for
|
||||||
|
free as a part of implementing AFIT which had been RFC'd previously. It
|
||||||
|
was then RFC'd independently in [RFC 3425], which was recently approved
|
||||||
|
by T-lang.
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
This doc is ordered mostly via the compilation pipeline. AST -> HIR ->
|
||||||
|
astconv -> typeck.
|
||||||
|
|
||||||
|
### AST and HIR
|
||||||
|
|
||||||
|
AST -> HIR lowering for RPITITs is almost the same as lowering RPITs. We
|
||||||
|
still lower them as
|
||||||
|
[`hir::ItemKind::OpaqueTy`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/hir/struct.OpaqueTy.html).
|
||||||
|
The two differences are that:
|
||||||
|
|
||||||
|
We record `in_trait` for the opaque. This will signify that the opaque
|
||||||
|
is an RPITIT for astconv, diagnostics that deal with HIR, etc.
|
||||||
|
|
||||||
|
We record `lifetime_mapping`s for the opaque type, described below.
|
||||||
|
|
||||||
|
#### Aside: Opaque lifetime duplication
|
||||||
|
|
||||||
|
*All opaques* (not just RPITITs) end up duplicating their captured
|
||||||
|
lifetimes into new lifetime parameters local to the opaque. The main
|
||||||
|
reason we do this is because RPITs need to be able to "reify"[^1] any
|
||||||
|
captured late-bound arguments, or make them into early-bound ones. This
|
||||||
|
is so they can be used as substs for the opaque, and later to
|
||||||
|
instantiate hidden types. Since we don't know which lifetimes are early-
|
||||||
|
or late-bound during AST lowering, we just do this for all lifetimes.
|
||||||
|
|
||||||
|
[^1]: This is compiler-errors terminology, I'm not claiming it's accurate :^)
|
||||||
|
|
||||||
|
The main addition for RPITITs is that during lowering we track the
|
||||||
|
relationship between the captured lifetimes and the corresponding
|
||||||
|
duplicated lifetimes in an additional field,
|
||||||
|
[`OpaqueTy::lifetime_mapping`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/hir/struct.OpaqueTy.html#structfield.lifetime_mapping).
|
||||||
|
We use this lifetime mapping later on in `predicates_of` to install
|
||||||
|
bounds that enforce equality between these duplicated lifetimes and
|
||||||
|
their source lifetimes in order to properly typecheck these GATs, which
|
||||||
|
will be discussed below.
|
||||||
|
|
||||||
|
##### note:
|
||||||
|
|
||||||
|
It may be better if we were able to lower without duplicates and for
|
||||||
|
that I think we would need to stop distinguishing between early and late
|
||||||
|
bound lifetimes. So we would need a solution like [Account for
|
||||||
|
late-bound lifetimes in generics
|
||||||
|
#103448](https://github.com/rust-lang/rust/pull/103448) and then also a
|
||||||
|
PR similar to [Inherit function lifetimes for impl-trait
|
||||||
|
#103449](https://github.com/rust-lang/rust/pull/103449).
|
||||||
|
|
||||||
|
### Astconv
|
||||||
|
|
||||||
|
The main change to astconv is that we lower `hir::TyKind::OpaqueDef` for
|
||||||
|
an RPITIT to a projection instead of an opaque, using a newly
|
||||||
|
synthesized def-id for a new associated type in the trait. We'll
|
||||||
|
describe how exactly we get this def-id in the next section.
|
||||||
|
|
||||||
|
This means that any time we call `ast_ty_to_ty` on the RPITIT, we end up
|
||||||
|
getting a projection back instead of an opaque. This projection can then
|
||||||
|
be normalized to the right value -- either the original opaque if we're
|
||||||
|
in the trait, or the inferred type of the RPITIT if we're in an impl.
|
||||||
|
|
||||||
|
#### Lowering to synthetic associated types
|
||||||
|
|
||||||
|
Using query feeding, we synthesize new associated types on both the
|
||||||
|
trait side and impl side for RPITITs that show up in methods.
|
||||||
|
|
||||||
|
##### Lowering RPITITs in traits
|
||||||
|
|
||||||
|
When `tcx.associated_item_def_ids(trait_def_id)` is called on a trait to
|
||||||
|
gather all of the trait's associated types, the query previously just
|
||||||
|
returned the def-ids of the HIR items that are children of the trait.
|
||||||
|
After [#112988], additionally, for each method in the trait, we add the
|
||||||
|
def-ids returned by
|
||||||
|
`tcx.associated_types_for_impl_traits_in_associated_fn(trait_method_def_id)`,
|
||||||
|
which walks through each trait method, gathers any RPITITs that show up
|
||||||
|
in the signature, and then calls
|
||||||
|
`associated_type_for_impl_trait_in_trait` for each RPITIT, which
|
||||||
|
synthesizes a new associated type.
|
||||||
|
|
||||||
|
##### Lowering RPITITs in impls
|
||||||
|
|
||||||
|
Similarly, along with the impl's HIR items, for each impl method, we
|
||||||
|
additionally add all of the
|
||||||
|
`associated_types_for_impl_traits_in_associated_fn` for the impl method.
|
||||||
|
This calls `associated_type_for_impl_trait_in_impl`, which will
|
||||||
|
synthesize an associated type definition for each RPITIT that comes from
|
||||||
|
the corresponding trait method.
|
||||||
|
|
||||||
|
#### Synthesizing new associated types
|
||||||
|
|
||||||
|
We use query feeding
|
||||||
|
([`TyCtxtAt::create_def`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/query/plumbing/struct.TyCtxtAt.html#method.create_def))
|
||||||
|
to synthesize a new def-id for the synthetic GATs for each RPITIT.
|
||||||
|
|
||||||
|
Locally, most of rustc's queries match on the HIR of an item to compute
|
||||||
|
their values. Since the RPITIT doesn't really have HIR associated with
|
||||||
|
it, or at least not HIR that corresponds to an associated type, we must
|
||||||
|
compute many queries eagerly and
|
||||||
|
[feed](https://github.com/rust-lang/rust/pull/104940) them, like
|
||||||
|
`opt_def_kind`, `associated_item`, `visibility`, and`defaultness`.
|
||||||
|
|
||||||
|
The values for most of these queries is obvious, since the RPITIT
|
||||||
|
conceptually inherits most of its information from the parent function
|
||||||
|
(e.g. `visibility`), or because it's trivially knowable because it's an
|
||||||
|
associated type (`opt_def_kind`).
|
||||||
|
|
||||||
|
Some other queries are more involved, or cannot be feeded, and we
|
||||||
|
document the interesting ones of those below:
|
||||||
|
|
||||||
|
##### `generics_of` for the trait
|
||||||
|
|
||||||
|
The GAT for an RPITIT conceptually inherits the same generics as the
|
||||||
|
RPIT it comes from. However, instead of having the method as the
|
||||||
|
generics' parent, the trait is the parent.
|
||||||
|
|
||||||
|
Currently we get away with taking the RPIT's generics and method
|
||||||
|
generics and flattening them both into a new generics list, preserving
|
||||||
|
the def-id of each of the parameters. (This may cause issues with
|
||||||
|
def-ids having the wrong parents, but in the worst case this will cause
|
||||||
|
diagnostics issues. If this ends up being an issue, we can synthesize
|
||||||
|
new def-ids for generic params whose parent is the GAT.)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>An illustrated example</b> </summary>
|
||||||
|
|
||||||
|
```rust
|
||||||
|
trait Foo {
|
||||||
|
fn method<'early: 'early, 'late, T>() -> impl Sized + Captures<'early, 'late>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Would desugar to...
|
||||||
|
```rust
|
||||||
|
trait Foo {
|
||||||
|
// vvvvvvvvv method's generics
|
||||||
|
// vvvvvvvvvvvvvvvvvvvvvvvv opaque's generics
|
||||||
|
type Gat<'early, T, 'early_duplicated, 'late>: Sized + Captures<'early_duplicated, 'late>;
|
||||||
|
|
||||||
|
fn method<'early: 'early, 'late, T>() -> Self::Gat<'early, T, 'early, 'late>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
##### `generics_of` for the impl
|
||||||
|
|
||||||
|
The generics for an impl's GAT are a bit more interesting. They are
|
||||||
|
composed of RPITIT's own generics (from the trait definition), appended
|
||||||
|
onto the impl's methods generics. This has the same issue as above,
|
||||||
|
where the generics for the GAT have parameters whose def-ids have the
|
||||||
|
wrong parent, but this should only cause issues in diagnostics.
|
||||||
|
|
||||||
|
We could fix this similarly if we were to synthesize new generics
|
||||||
|
def-ids, but this can be done later in a forwards-compatible way,
|
||||||
|
perhaps by a interested new contributor.
|
||||||
|
|
||||||
|
##### `opt_rpitit_info`
|
||||||
|
|
||||||
|
Some queries rely on computing information that would result in cycles
|
||||||
|
if we were to feed them eagerly, like `explicit_predicates_of`.
|
||||||
|
Therefore we defer to the `predicates_of` provider to return the right
|
||||||
|
value for our RPITIT's GAT. We do this by detecting early on in the
|
||||||
|
query if the associated type is synthetic by using
|
||||||
|
[`opt_rpitit_info`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.opt_rpitit_info),
|
||||||
|
which returns `Some` if the associated type is synthetic.
|
||||||
|
|
||||||
|
Then, during a query like `explicit_predicates_of`, we can detect if an
|
||||||
|
associated type is synthetic like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ... {
|
||||||
|
if let Some(rpitit_info) = tcx.opt_rpitit_info(def_id) {
|
||||||
|
// Do something special for RPITITs...
|
||||||
|
return ...;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The regular computation which relies on access to the HIR of `def_id`.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `explicit_predicates_of`
|
||||||
|
|
||||||
|
RPITITs begin by copying the predicates of the method that defined it,
|
||||||
|
both on the trait and impl side.
|
||||||
|
|
||||||
|
Additionally, we install "bidirectional outlives" predicates.
|
||||||
|
Specifically, we add region-outlives predicates in both directions for
|
||||||
|
each captured early-bound lifetime that constrains it to be equal to the
|
||||||
|
duplicated early-bound lifetime that results from lowering. This is best
|
||||||
|
illustrated in an example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
trait Foo<'a> {
|
||||||
|
fn bar() -> impl Sized + 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desugars into...
|
||||||
|
|
||||||
|
trait Foo<'a> {
|
||||||
|
type Gat<'a_duplicated>: Sized + 'a
|
||||||
|
where
|
||||||
|
'a: 'a_duplicated,
|
||||||
|
'a_duplicated: 'a;
|
||||||
|
//~^ Specifically, we should be able to assume that the
|
||||||
|
// duplicated `'a_duplicated` lifetime always stays in
|
||||||
|
// sync with the `'a` lifetime.
|
||||||
|
|
||||||
|
fn bar() -> Self::Gat<'a>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `assumed_wf_types`
|
||||||
|
|
||||||
|
The GATs in both the trait and impl inherit the `assumed_wf_types` of
|
||||||
|
the trait method that defines the RPITIT. This is to make sure that the
|
||||||
|
following code is well formed when lowered.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
trait Foo {
|
||||||
|
fn iter<'a, T>(x: &'a [T]) -> impl Iterator<Item = &'a T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// which is lowered to...
|
||||||
|
|
||||||
|
trait FooDesugared {
|
||||||
|
type Iter<'a, T>: Iterator<Item = &'a T>;
|
||||||
|
//~^ assumed wf: `&'a [T]`
|
||||||
|
// Without assumed wf types, the GAT would not be well-formed on its own.
|
||||||
|
|
||||||
|
fn iter<'a, T>(x: &'a [T]) -> Self::Iter<'a, T>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Because `assumed_wf_types` is only defined for local def ids, in order
|
||||||
|
to properly implement `assumed_wf_types` for impls of foreign traits
|
||||||
|
with RPITs, we need to encode the assumed wf types of RPITITs in an
|
||||||
|
extern query
|
||||||
|
[`assumed_wf_types_for_rpitit`](https://github.com/rust-lang/rust/blob/a17c7968b727d8413801961fc4e89869b6ab00d3/compiler/rustc_ty_utils/src/implied_bounds.rs#L14).
|
||||||
|
|
||||||
|
### Typechecking
|
||||||
|
|
||||||
|
#### The RPITIT inference algorithm
|
||||||
|
|
||||||
|
The RPITIT inference algorithm is implemented in
|
||||||
|
[`collect_return_position_impl_trait_in_trait_tys`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_analysis/check/compare_impl_item/fn.collect_return_position_impl_trait_in_trait_tys.html).
|
||||||
|
|
||||||
|
**High-level:** Given a impl method and a trait method, we take the
|
||||||
|
trait method and instantiate each RPITIT in the signature with an infer
|
||||||
|
var. We then equate this trait method signature with the impl method
|
||||||
|
signature, and process all obligations that fall out in order to infer
|
||||||
|
the type of all of the RPITITs in the method.
|
||||||
|
|
||||||
|
The method is also responsible for making sure that the hidden types for
|
||||||
|
each RPITIT actually satisfy the bounds of the `impl Trait`, i.e. that
|
||||||
|
if we infer `impl Trait = Foo`, that `Foo: Trait` holds.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>An example...</b></summary>
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![feature(return_position_impl_trait_in_trait)]
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
trait Foo {
|
||||||
|
fn bar() -> impl Deref<Target = impl Sized>;
|
||||||
|
// ^- RPITIT ?0 ^- RPITIT ?1
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Foo for () {
|
||||||
|
fn bar() -> Box<String> { Box::new(String::new()) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We end up with the trait signature that looks like `fn() -> ?0`, and
|
||||||
|
nested obligations `?0: Deref<Target = ?1>`, `?1: Sized`. The impl
|
||||||
|
signature is `fn() -> Box<String>`.
|
||||||
|
|
||||||
|
Equating these signatures gives us `?0 = Box<String>`, which then after
|
||||||
|
processing the obligation `Box<String>: Deref<Target = ?1>` gives us `?1
|
||||||
|
= String`, and the other obligation `String: Sized` evaluates to true.
|
||||||
|
|
||||||
|
By the end of the algorithm, we end up with a mapping between associated
|
||||||
|
type def-ids to concrete types inferred from the signature. We can then
|
||||||
|
use this mapping to implement `type_of` for the synthetic associated
|
||||||
|
types in the impl, since this mapping describes the type that should
|
||||||
|
come after the `=` in `type Assoc = ...` for each RPITIT.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Default trait body
|
||||||
|
|
||||||
|
Type-checking a default trait body, like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
trait Foo {
|
||||||
|
fn bar() -> impl Sized {
|
||||||
|
1i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
requires one interesting hack. We need to install a projection predicate
|
||||||
|
into the param-env of `Foo::bar` allowing us to assume that the RPITIT's
|
||||||
|
GAT normalizes to the RPITIT's opaque type. This relies on the
|
||||||
|
observation that a trait method and RPITIT's GAT will always be "in
|
||||||
|
sync". That is, one will only ever be overridden if the other one is as
|
||||||
|
well.
|
||||||
|
|
||||||
|
Compare this to a similar desugaring of the code above, which would fail
|
||||||
|
because we cannot rely on this same assumption:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
#![feature(associated_type_defaults)]
|
||||||
|
|
||||||
|
trait Foo {
|
||||||
|
type RPITIT = impl Sized;
|
||||||
|
|
||||||
|
fn bar() -> Self::RPITIT {
|
||||||
|
01i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Failing because a down-stream impl could theoretically provide an
|
||||||
|
implementation for `RPITIT` without providing an implementation of
|
||||||
|
`foo`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
error[E0308]: mismatched types
|
||||||
|
--> src/lib.rs:8:9
|
||||||
|
|
|
||||||
|
5 | type RPITIT = impl Sized;
|
||||||
|
| ------------------------- associated type defaults can't be assumed inside the trait defining them
|
||||||
|
6 |
|
||||||
|
7 | fn bar() -> Self::RPITIT {
|
||||||
|
| ------------ expected `<Self as Foo>::RPITIT` because of return type
|
||||||
|
8 | 01i32
|
||||||
|
| ^^^^^ expected associated type, found `i32`
|
||||||
|
|
|
||||||
|
= note: expected associated type `<Self as Foo>::RPITIT`
|
||||||
|
found type `i32`
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Well-formedness checking
|
||||||
|
|
||||||
|
We check well-formedness of RPITITs just like regular associated types.
|
||||||
|
|
||||||
|
Since we added lifetime bounds in `predicates_of` that link the
|
||||||
|
duplicated early-bound lifetimes to their original lifetimes, and we
|
||||||
|
implemented `assumed_wf_types` which inherits the WF types of the method
|
||||||
|
from which the RPITIT originates ([#113704]), we have no issues
|
||||||
|
WF-checking the GAT as if it were a regular GAT.
|
||||||
|
|
||||||
|
### What's broken, what's weird, etc.
|
||||||
|
|
||||||
|
##### Specialization is super busted
|
||||||
|
|
||||||
|
The "default trait methods" described above does not interact well with
|
||||||
|
specialization, because we only install those projection bounds in trait
|
||||||
|
default methods, and not in impl methods. Given that specialization is
|
||||||
|
already pretty busted, I won't go into detail, but it's currently a bug
|
||||||
|
tracked in:
|
||||||
|
* `tests/ui/impl-trait/in-trait/specialization-broken.rs`
|
||||||
|
|
||||||
|
##### Projections don't have variances
|
||||||
|
|
||||||
|
This code fails because projections don't have variances:
|
||||||
|
```rust
|
||||||
|
#![feature(return_position_impl_trait_in_trait)]
|
||||||
|
|
||||||
|
trait Foo {
|
||||||
|
// Note that the RPITIT below does *not* capture `'lt`.
|
||||||
|
fn bar<'lt: 'lt>() -> impl Eq;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test<'a, 'b, T: Foo>() -> bool {
|
||||||
|
<T as Foo>::bar::<'a>() == <T as Foo>::bar::<'b>()
|
||||||
|
//~^ ERROR
|
||||||
|
// (requires that `'a == 'b`)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is because we can't relate `<T as Foo>::Rpitit<'a>` and `<T as
|
||||||
|
Foo>::Rpitit<'b>`, even if they don't capture their lifetime. If we were
|
||||||
|
using regular opaque types, this would work, because they would be
|
||||||
|
bivariant in that lifetime parameter:
|
||||||
|
```rust
|
||||||
|
#![feature(return_position_impl_trait_in_trait)]
|
||||||
|
|
||||||
|
fn bar<'lt: 'lt>() -> impl Eq {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test<'a, 'b>() -> bool {
|
||||||
|
bar::<'a>() == bar::<'b>()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is probably okay though, since RPITITs will likely have their
|
||||||
|
captures behavior changed to capture all in-scope lifetimes anyways.
|
||||||
|
This could also be relaxed later in a forwards-compatible way if we were
|
||||||
|
to consider variances of RPITITs when relating projections.
|
||||||
|
|
||||||
|
[#112988]: https://github.com/rust-lang/rust/pull/112988
|
||||||
|
[RFC 3425]: https://github.com/rust-lang/rfcs/pull/3425
|
||||||
|
[#101224]: https://github.com/rust-lang/rust/pull/101224
|
||||||
|
[#113704]: https://github.com/rust-lang/rust/pull/113704
|
||||||
Loading…
Reference in New Issue