|
1 |
| -# Effects and effect checking |
2 |
| - |
3 |
| -Note: all of this describes the implementation of the unstable `effects` and |
4 |
| -`const_trait_impl` features. None of this implementation is usable or visible from |
5 |
| -stable Rust. |
6 |
| - |
7 |
| -The implementation of const traits and `~const` bounds is a limited effect system. |
8 |
| -It is used to allow trait bounds on `const fn` to be used within the `const fn` for |
9 |
| -method calls. Within the function, in order to know whether a method on a trait |
10 |
| -bound is `const`, we need to know whether there is a `~const` bound for the trait. |
11 |
| -In order to know whether we can instantiate a `~const` bound on a `const fn`, we |
12 |
| -need to know whether there is a `const_trait` impl for the type and trait being |
13 |
| -used (or whether the `const fn` is used at runtime, then any type implementing the |
14 |
| -trait is ok, just like with other bounds). |
15 |
| - |
16 |
| -We perform these checks via a const generic boolean that gets attached to all |
17 |
| -`const fn` and `const trait`. The following sections will explain the desugarings |
18 |
| -and the way we perform the checks at call sites. |
19 |
| - |
20 |
| -The const generic boolean is inverted to the meaning of `const`. In the compiler |
21 |
| -it is called `host`, because it enables "host APIs" like `static` items, network |
22 |
| -access, disk access, random numbers and everything else that isn't available in |
23 |
| -`const` contexts. So `false` means "const", `true` means "not const" and if it's |
24 |
| -a generic parameter, it means "maybe const" (meaning we're in a const fn or const |
25 |
| -trait). |
26 |
| - |
27 |
| -## `const fn` |
28 |
| - |
29 |
| -All `const fn` have a `#[rustc_host] const host: bool` generic parameter that is |
30 |
| -hidden from users. Any `~const Trait` bounds in the generics list or `where` bounds |
31 |
| -of a `const fn` get converted to `Trait<host> + Trait<true>` bounds. The `Trait<true>` |
32 |
| -exists so that associated types of the generic param can be used from projections |
33 |
| -like `<T as Trait>::Assoc`, because there are no `<T as ~const Trait>` projections for now. |
34 |
| - |
35 |
| -## `#[const_trait] trait`s |
36 |
| - |
37 |
| -The `#[const_trait]` attribute gives the marked trait a `#[rustc_host] const host: bool` |
38 |
| -generic parameter. All functions of the trait "inherit" this generic parameter, just like |
39 |
| -they have all the regular generic parameters of the trait. Any `~const Trait` super-trait |
40 |
| -bounds get desugared to `Trait<host> + Trait<true>` in order to allow using associated |
41 |
| -types and consts of the super traits in the trait declaration. This is necessary, because |
42 |
| -`<Self as SuperTrait>::Assoc` is always `<Self as SuperTrait<true>>::Assoc` as there is |
43 |
| -no `<Self as ~const SuperTrait>` syntax. |
44 |
| - |
45 |
| -## `typeck` performing method and function call checks. |
46 |
| - |
47 |
| -When generic parameters are instantiated for any items, the `host` generic parameter |
48 |
| -is always instantiated as an inference variable. This is a special kind of inference var |
49 |
| -that is not part of the type or const inference variables, similar to how we have |
50 |
| -special inference variables for type variables that we know to be an integer, but not |
51 |
| -yet which one. These separate inference variables fall back to `true` at |
52 |
| -the end of typeck (in `fallback_effects`) to ensure that `let _ = some_fn_item_name;` |
53 |
| -will keep compiling. |
54 |
| - |
55 |
| -All actually used (in function calls, casts, or anywhere else) function items, will |
56 |
| -have the `enforce_context_effects` method invoked. |
57 |
| -It trivially returns if the function being called has no `host` generic parameter. |
58 |
| - |
59 |
| -In order to error if a non-const function is called in a const context, we have not |
60 |
| -yet disabled the const-check logic that happens on MIR, because |
61 |
| -`enforce_context_effects` does not yet perform this check. |
62 |
| - |
63 |
| -The function call's `host` parameter is then equated to the context's `host` value, |
64 |
| -which almost always trivially succeeds, as it was an inference var. If the inference |
65 |
| -var has already been bound (since the function item is invoked twice), the second |
66 |
| -invocation checks it against the first. |
| 1 | +# Effects and const condition checking |
| 2 | + |
| 3 | +## The `HostEffect` predicate |
| 4 | + |
| 5 | +[`HostEffectPredicate`]s are a kind of predicate from `~const Tr` or `const Tr` |
| 6 | +bounds. It has a trait reference, and a `constness` which could be `Maybe` or |
| 7 | +`Const` depending on the bound. Because `~const Tr`, or rather `Maybe` bounds |
| 8 | +apply differently based on whichever contexts they are in, they have different |
| 9 | +behavior than normal bounds. Where normal trait bounds on a function such as |
| 10 | +`T: Tr` are collected within the [`predicates_of`] query to be proven when a |
| 11 | +function is called and to be assumed within the function, bounds such as |
| 12 | +`T: ~const Tr` will behave as a normal trait bound and add `T: Tr` to the result |
| 13 | +from `predicates_of`, but also adds a `HostEffectPredicate` to the |
| 14 | +[`const_conditions`] query. |
| 15 | + |
| 16 | +On the other hand, `T: const Tr` bounds do not change meaning across contexts, |
| 17 | +therefore they will result in `HostEffect(T: Tr, const)` being added to |
| 18 | +`predicates_of`, and not `const_conditions`. |
| 19 | + |
| 20 | +[`HostEffectPredicate`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/predicate/struct.HostEffectPredicate.html |
| 21 | +[`predicates_of`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.predicates_of |
| 22 | +[`const_conditions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.const_conditions |
| 23 | + |
| 24 | +## The `const_conditions` query |
| 25 | + |
| 26 | +`predicates_of` represents a set of predicates that need to be proven to use an |
| 27 | +item. For example, to use `foo` in the example below: |
| 28 | + |
| 29 | +```rust |
| 30 | +fn foo<T>() where T: Default {} |
| 31 | +``` |
| 32 | + |
| 33 | +We must be able to prove that `T` implements `Default`. In a similar vein, |
| 34 | +`const_conditions` represents a set of predicates that need to be proven to use |
| 35 | +an item *in const contexts*. If we adjust the example above to use `const` trait |
| 36 | +bounds: |
| 37 | + |
| 38 | +```rust |
| 39 | +const fn foo<T>() where T: ~const Default {} |
| 40 | +``` |
| 41 | + |
| 42 | +Then `foo` would get a `HostEffect(T: Default, maybe)` in the `const_conditions` |
| 43 | +query, suggesting that in order to call `foo` from const contexts, one must |
| 44 | +prove that `T` has a const implementation of `Default`. |
| 45 | + |
| 46 | +## Enforcement of `const_conditions` |
| 47 | + |
| 48 | +`const_conditions` are currently checked in various places. |
| 49 | + |
| 50 | +Every call in HIR from a const context (which includes `const fn` and `const` |
| 51 | +items) will check that `const_conditions` of the function we are calling hold. |
| 52 | +This is done in [`FnCtxt::enforce_context_effects`]. Note that we don't check |
| 53 | +if the function is only referred to but not called, as the following code needs |
| 54 | +to compile: |
| 55 | + |
| 56 | +```rust |
| 57 | +const fn hi<T: ~const Default>() -> T { |
| 58 | + T::default() |
| 59 | +} |
| 60 | +const X: fn() -> u32 = hi::<u32>; |
| 61 | +``` |
| 62 | + |
| 63 | +For a trait `impl` to be well-formed, we must be able to prove the |
| 64 | +`const_conditions` of the trait from the `impl`'s environment. This is checked |
| 65 | +in [`wfcheck::check_impl`]. |
| 66 | + |
| 67 | +Here's an example: |
| 68 | + |
| 69 | +```rust |
| 70 | +#[const_trait] |
| 71 | +trait Bar {} |
| 72 | +#[const_trait] |
| 73 | +trait Foo: ~const Bar {} |
| 74 | +// `const_conditions` contains `HostEffect(Self: Bar, maybe)` |
| 75 | + |
| 76 | +impl const Bar for () {} |
| 77 | +impl const Foo for () {} |
| 78 | +// ^ here we check `const_conditions` for the impl to be well-formed |
| 79 | +``` |
| 80 | + |
| 81 | +Methods of trait impls must not have stricter bounds than the method of the |
| 82 | +trait that they are implementing. To check that the methods are compatible, a |
| 83 | +hybrid environment is constructed with the predicates of the `impl` plus the |
| 84 | +predicates of the trait method, and we attempt to prove the predicates of the |
| 85 | +impl method. We do the same for `const_conditions`: |
| 86 | + |
| 87 | +```rust |
| 88 | +#[const_trait] |
| 89 | +trait Foo { |
| 90 | + fn hi<T: ~const Default>(); |
| 91 | +} |
| 92 | + |
| 93 | +impl<T: ~const Clone> Foo for Vec<T> { |
| 94 | + fn hi<T: ~const PartialEq>(); |
| 95 | + // ^ we can't prove `T: ~const PartialEq` given `T: ~const Clone` and |
| 96 | + // `T: ~const Default`, therefore we know that the method on the impl |
| 97 | + // is stricter than the method on the trait. |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +These checks are done in [`compare_method_predicate_entailment`]. A similar |
| 102 | +function that does the same check for associated types is called |
| 103 | +[`compare_type_predicate_entailment`]. Both of these need to consider |
| 104 | +`const_conditions` when in const contexts. |
| 105 | + |
| 106 | +In MIR, as part of const checking, `const_conditions` of items that are called |
| 107 | +are revalidated again in [`Checker::revalidate_conditional_constness`]. |
| 108 | + |
| 109 | +[`compare_method_predicate_entailment`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_analysis/check/compare_impl_item/fn.compare_method_predicate_entailment.html |
| 110 | +[`compare_type_predicate_entailment`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_analysis/check/compare_impl_item/fn.compare_type_predicate_entailment.html |
| 111 | +[`FnCtxt::enforce_context_effects`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/fn_ctxt/struct.FnCtxt.html#method.enforce_context_effects |
| 112 | +[`wfcheck::check_impl`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_analysis/check/wfcheck/fn.check_impl.html |
| 113 | +[`Checker::revalidate_conditional_constness`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_const_eval/check_consts/check/struct.Checker.html#method.revalidate_conditional_constness |
| 114 | + |
| 115 | +## `explicit_implied_const_bounds` on associated types and traits |
| 116 | + |
| 117 | +Bounds on associated types, opaque types, and supertraits such as |
| 118 | +```rust |
| 119 | +trait Foo: ~const PartialEq { |
| 120 | + type X: ~const PartialEq; |
| 121 | +} |
| 122 | + |
| 123 | +fn foo() -> impl ~const PartialEq { |
| 124 | + // ^ unimplemented syntax |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +Have their bounds represented differently. Unlike `const_conditions` which need |
| 129 | +to be proved for callers, and can be assumed inside the definition (e.g. trait |
| 130 | +bounds on functions), these bounds need to be proved at definition (at the impl, |
| 131 | +or when returning the opaque) but can be assumed for callers. The non-const |
| 132 | +equivalent of these bounds are called [`explicit_item_bounds`]. |
| 133 | + |
| 134 | +These bounds are checked in [`compare_impl_item::check_type_bounds`] for HIR |
| 135 | +typeck, [`evaluate_host_effect_from_item_bounds`] in the old solver and |
| 136 | +[`consider_additional_alias_assumptions`] in the new solver. |
| 137 | + |
| 138 | +[`explicit_item_bounds`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.explicit_item_bounds |
| 139 | +[`compare_impl_item::check_type_bounds`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_analysis/check/compare_impl_item/fn.check_type_bounds.html |
| 140 | +[`evaluate_host_effect_from_item_bounds`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/traits/effects/fn.evaluate_host_effect_from_item_bounds.html |
| 141 | +[`consider_additional_alias_assumptions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_next_trait_solver/solve/assembly/trait.GoalKind.html#tymethod.consider_additional_alias_assumptions |
| 142 | + |
| 143 | +## Proving `HostEffectPredicate`s |
| 144 | + |
| 145 | +`HostEffectPredicate`s are implemented both in the [old solver] and the [new |
| 146 | +trait solver]. In general, we can prove a `HostEffect` predicate when either of |
| 147 | +these conditions are met: |
| 148 | + |
| 149 | +* The predicate can be assumed from caller bounds; |
| 150 | +* The type has a `const` `impl` for the trait, *and* that const conditions on |
| 151 | +the impl holds, *and* that the `explicit_implied_const_bounds` on the trait |
| 152 | +holds; or |
| 153 | +* The type has a built-in implementation for the trait in const contexts. For |
| 154 | +example, `Fn` may be implemented by function items if their const conditions |
| 155 | +are satisfied, or `Destruct` is implemented in const contexts if the type can |
| 156 | +be dropped at compile time. |
| 157 | + |
| 158 | +[old solver]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_trait_selection/traits/effects.rs.html |
| 159 | +[new trait solver]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_next_trait_solver/solve/effect_goals.rs.html |
0 commit comments