|
| 1 | +# Opaque types in the new solver |
| 2 | + |
| 3 | +The way opaque types are handled in the new solver differs from the old implementation. |
| 4 | +This should be a self-contained explanation of the behavior in the new solver. |
| 5 | + |
| 6 | +## opaques are alias types |
| 7 | + |
| 8 | +Opaque types are treated the same as other aliases, most notabily associated types, |
| 9 | +whenever possible. There should be as few divergences in behavior as possible. |
| 10 | + |
| 11 | +This is desirable, as they are very similar to other alias types, in that they can be |
| 12 | +normalized to their hidden type and also have the same requirements for completeness. |
| 13 | +Treating them this way also reduces the complexity of the type system by sharing code. |
| 14 | +Having to deal with opaque types separately results in more complex rules and new kinds |
| 15 | +of interactions. As we need to treat them like other aliases in the implicit-negative |
| 16 | +mode, having significant differences between modes also adds complexity. |
| 17 | + |
| 18 | +*open question: is there an alternative approach here, maybe by treating them more like rigid |
| 19 | +types with more limited places to instantiate them? they would still have to be ordinary |
| 20 | +aliases during coherence* |
| 21 | + |
| 22 | +### `normalizes-to` for opaques |
| 23 | + |
| 24 | +[source][norm] |
| 25 | + |
| 26 | +`normalizes-to` is used to define the one-step normalization behavior for aliases in the new |
| 27 | +solver: `<<T as IdInner>::Assoc as IdOuter>::Assoc` first normalizes to `<T as IdInner>::Assoc` |
| 28 | +which then normalizes to `T`. It takes both the `AliasTy` which is getting normalized and the |
| 29 | +expected `Term`. To use `normalizes-to` for actual normalization, the expected term can simply |
| 30 | +be an unconstrained inference variable. |
| 31 | + |
| 32 | +For opaque types in the defining scope and in the implicit-negative coherence mode, this is |
| 33 | +always done in two steps. Outside of the defining scope `normalizes-to` for opaques always |
| 34 | +returns `Err(NoSolution)`. |
| 35 | + |
| 36 | +We start by trying to to assign the expected type as a hidden type. |
| 37 | + |
| 38 | +In the implicit-negative coherence mode, this currently always results in ambiguity without |
| 39 | +interacting with the opaque types storage. We could instead add allow 'defining' all opaque types, |
| 40 | +discarding their inferred types at the end, changing the behavior of an opaque type is used |
| 41 | +multiple times during coherence: [example][coherence-example] |
| 42 | + |
| 43 | +Inside of the defining scope we start by checking whether the type and const arguments of the |
| 44 | +opaque are all placeholders: [source](placeholder-ck). If this check is ambiguous, |
| 45 | +return ambiguity, if it fails, return `Err(NoSolution)`. This check ignores regions which are |
| 46 | +only checked at the end of borrowck. If it succeeds, continue. |
| 47 | + |
| 48 | +We then check whether we're able to *semantically* unify the generic arguments of the opaque |
| 49 | +with the arguments of any opaque type already in the opaque types storage. If so, we unify the |
| 50 | +previously stored type with the expected type of this `normalizes-to` call: [source][eq-prev][^1]. |
| 51 | + |
| 52 | +If not, we insert the expected type in the opaque types storage: [source][insert-storage][^2]. |
| 53 | +Finally, we check whether the item bounds of the opaque hold for the expected type: [source]. |
| 54 | + |
| 55 | +[norm]: https://github.com/rust-lang/rust/blob/384d26fc7e3bdd7687cc17b2662b091f6017ec2a/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs#L13 |
| 56 | +[coherence-example]: https://github.com/rust-lang/rust/blob/master/tests/ui/type-alias-impl-trait/coherence_different_hidden_ty.rs |
| 57 | +[placeholder-ck]: https://github.com/rust-lang/rust/blob/384d26fc7e3bdd7687cc17b2662b091f6017ec2a/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs#L33 |
| 58 | +[check-storage]: https://github.com/rust-lang/rust/blob/384d26fc7e3bdd7687cc17b2662b091f6017ec2a/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs#L51-L52 |
| 59 | +[eq-prev]: https://github.com/rust-lang/rust/blob/384d26fc7e3bdd7687cc17b2662b091f6017ec2a/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs#L51-L59 |
| 60 | +[insert-storage]: https://github.com/rust-lang/rust/blob/384d26fc7e3bdd7687cc17b2662b091f6017ec2a/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs#L68 |
| 61 | +[item-bounds-ck]: https://github.com/rust-lang/rust/blob/384d26fc7e3bdd7687cc17b2662b091f6017ec2a/compiler/rustc_trait_selection/src/solve/normalizes_to/opaque_types.rs#L69-L74 |
| 62 | +[^1]: FIXME: this should ideally only result in a unique candidate given that we require the args to be placeholders and regions are always inference vars |
| 63 | +[^2]: FIXME: why do we check whether the expected type is rigid for this. |
| 64 | + |
| 65 | +### using alias-bounds of normalizable aliases |
| 66 | + |
| 67 | +https://github.com/rust-lang/trait-system-refactor-initiative/issues/77 |
| 68 | + |
| 69 | +Using an `AliasBound` candidate for normalizable aliases is generally not possible as an |
| 70 | +associated type can have stronger bounds then the resulting type when normalizing via a |
| 71 | +`ParamEnv` candidate. |
| 72 | + |
| 73 | +These candidates would change our exact normalization strategy to be user-facing. It is otherwise |
| 74 | +pretty much unobservable whether we eagerly normalize. Where we normalize is something we likely |
| 75 | +want to change that after removing support for the old solver, so that would be undesirable. |
| 76 | + |
| 77 | +## opaque types can be defined anywhere |
| 78 | + |
| 79 | +Opaque types in their defining-scope can be defined anywhere, whether when simply relating types |
| 80 | +or in the trait solver. This removes order dependence and incompleteness. Without this the result |
| 81 | +of a goal can differ due to subtle reasons, e.g. whether we try to evaluate a goal using the |
| 82 | +opaque before the first defining use of the opaque. |
| 83 | + |
| 84 | +## higher ranked opaque types in their defining scope |
| 85 | + |
| 86 | +These are not supported and trying to define them right now should always error. |
| 87 | + |
| 88 | +FIXME: Because looking up opaque types in the opaque type storage can now unify regions, |
| 89 | +we have to eagerly check that the opaque types does not reference placeholders. We otherwise |
| 90 | +end up leaking placeholders. |
| 91 | + |
| 92 | +## member constraints |
| 93 | + |
| 94 | +The handling of member constraints does not change in the new solver. See the |
| 95 | +[relevant existing chapter][member-constraints] for that. |
| 96 | + |
| 97 | +[member-constraints]: https://rustc-dev-guide.rust-lang.org/borrow_check/region_inference/member_constraints.html |
| 98 | + |
| 99 | +## calling methods on opaque types |
| 100 | + |
| 101 | +FIXME: We need to continue to support calling methods on still unconstrained |
| 102 | +opaque types in their defining scope. It's unclear how to best do this. |
| 103 | +```rust |
| 104 | +use std::future::Future; |
| 105 | +use futures::FutureExt; |
| 106 | + |
| 107 | +fn go(i: usize) -> impl Future<Output = ()> + Send + 'static { |
| 108 | + async move { |
| 109 | + if i != 0 { |
| 110 | + // This returns `impl Future<Output = ()>` in its defining scope, |
| 111 | + // we don't know the concrete type of that opaque at this point. |
| 112 | + // Currently treats the opaque as a known type and succeeds, but |
| 113 | + // from the perspective of "easiest to soundly implement", it would |
| 114 | + // be good for this to be ambiguous. |
| 115 | + go(i - 1).boxed().await; |
| 116 | + } |
| 117 | + } |
| 118 | +} |
| 119 | +``` |
0 commit comments