You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Use the recorded types in MIR to determine generator auto-trait implementations
When we construct a generator type, we build a `ty::GeneratorWitness`
from a list of types that may live across a suspend point. These types are
used to determine the 'constitutent types' for a generator when
selecting an auto-trait predicate. Any types appearing in the
GeneratorWitness are required to implement the auto trait (e.g. `Send`
or `Sync`).
This analysis
is based on the HIR - as a result, it is unable to take liveness of
variables into account. This often results in unnecessary bounds being
computing (e.g requiring that a `Rc` be `Sync` even if it is dropped
before a suspend point), making generators and `async fn`s much less
ergonomic to write.
This commit uses the generator MIR to determine the actual 'constituent
types' of a generator. Specifically, a type in the generator witness is
considered to be a 'constituent type' of the witness if it appears in
the `field_tys` of the computed `GeneratorLayout`. Any type which is
stored across an suspend point must be a constituent type (since it could
be used again after a suspend), while any type that is not stored across
a suspend point cannot possible be a constituent type (since it is
impossible for it to be used again).
By re-using the existing generator layout computation logic, we get some
nice properties for free:
* Types which are dead before the suspend point are not considered
constituent types
* Types without Drop impls not considered constituent types if their
scope extends across an await point (assuming that they are never used
after an await point).
Note that this only affects `ty::GeneratorWitness`, *not*
`ty::Generator` itself. Upvars (captured types from the parent scope)
are considered to be constituent types of the base `ty::Generator`, not
the inner `ty::GeneratorWitness`. This means that upvars are always
considered constituent types - this is because by defintion, they always
live across the first implicit suspend point.
-------
Implementation:
The most significant part of this PR is the introduction of a new
'delayed generator witness mode' to `TraitEngine`. As @nikomatsakis
pointed out, attmepting to compute generator MIR during type-checking
results in the following cycle:
1. We attempt to type-check a generator's parent function
2. During type checking of the parent function, we record a
predicate of the form `<generator>: AutoTrait`
3. We add this predicate to a `TraitEngine`, and attempt to fulfill it.
4. When we atempt to select the predicate, we attempt to compute the MIR
for `<generator>`
5. The MIR query attempts to compute `type_of(generator_def_id)`, which
results in us attempting to type-check the generator's parent function.
To break this cycle, we defer processing of all auto-trait predicates
involving `ty::GeneratorWitness`. These predicates are recorded in the
`TypeckTables` for the parent function. During MIR type-checking of the
parent function, we actually attempt to fulfill these predicates,
reporting any errors that occur.
The rest of the PR is mostly fallout from this change:
* `ty::GeneratorWitness` now stores the `DefId` of its generator. This
allows us to retrieve the MIR for the generator when `SelectionContext`
processes a predicate involving a `ty::GeneratorWitness`
* Since we now store `PredicateObligations` in `TypeckTables`, several
different types have now become `RustcEncodable`/`RustcDecodable`. These
are purely mechanical changes (adding new `#[derives]`), with one
exception - a new `SpecializedDecoder` impl for `List<Predicate>`.
This was essentially identical to other `SpecializedDecoder` imps, but it
would be good to have someone check it over.
* When we delay processing of a `Predicate`, we move it from one
`InferCtxt` to another. This requires us to prevent any inference
variables from leaking out from the first `InferCtxt` - if used in
another `InferCtxt`, they will either be non-existent or refer to the
the wrong variable. Fortunately, the predicate itself has no region
variables - the `ty::GeneratorWitness` has only late-bound regions,
while auto-traits have no generic parameters whatsoever.
However, we still need to deal with the `ObligationCause` stored by the
`PredicateObligation`. An `ObligationCause` (or a nested cause) may have
any number of region variables stored inside it (e.g. from stored
types). Luckily, `ObligationCause` is only used for error reporting, so
we can safely erase all regions variables from it, without affecting the
actual processing of the obligation.
To accomplish this, I took the somewhat unusual approach of implementing
`TypeFoldable` for `ObligationCause`, but did *not* change the `TypeFoldable`
implementation of `Obligation` to fold its contained
`ObligationCause. Other than this one odd case, all other callers of
`TypeFoldable` have no interest in folding an `ObligationCause`. As a
result, we explicitly fold the `ObligationCause` when computing our
deferred generator witness predicates. Since `ObligationCause` is only
used for displaying error messages, the worst that can happen is that a
slightly odd error message is displayed to a user.
With this change, several tests now have fewer errors than they did
previously, due to the improved generator analysis. Unfortunately, this
does not resolve issue rust-lang#64960. The MIR generator transformation stores
format temporaries in the generator, due to the fact that the `format!`
macro takes a reference to them. As a result, they are still considered
constituent types of the `GeneratorWitness`, and are still required to
implement `Send` and `Sync.
* I've changed the pretty-printing of `ty::GeneratorWitness` to print
out its generator DefId, as well as the word `witness`. This makes
debugging issues related to the computation of constituent types much
simpler.
As a final note, this PR contains one unrelated change - all generators
now implement `Freeze` unconditionally. I've opened a separate PR
containing this change - however, it's necessary to allow this branch to
compile without cycle errors. I've left it in this PR to make it easy to
test out this branch on actual code. Assuming that it is accepted, this
PR will be rebased against `master` when it is merged. Otherwise, I'll
need to figure out a different approach to generator
const-qualification.
0 commit comments