Merge pull request #2258 from fee1-dead-contrib/constck

Rewrite effects checking chapter
This commit is contained in:
Oli Scherer 2025-03-10 12:35:40 +01:00 committed by GitHub
commit 7119746828
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 147 additions and 54 deletions

View File

@ -178,7 +178,7 @@
- [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) - [Return Position Impl Trait In Trait](./return-position-impl-trait-in-trait.md)
- [Region inference restrictions][opaque-infer] - [Region inference restrictions][opaque-infer]
- [Effect checking](./effects.md) - [Const condition checking](./effects.md)
- [Pattern and Exhaustiveness Checking](./pat-exhaustive-checking.md) - [Pattern and Exhaustiveness Checking](./pat-exhaustive-checking.md)
- [Unsafety Checking](./unsafety-checking.md) - [Unsafety Checking](./unsafety-checking.md)
- [MIR dataflow](./mir/dataflow.md) - [MIR dataflow](./mir/dataflow.md)

View File

@ -1,66 +1,159 @@
# Effects and effect checking # Effects and const condition checking
Note: all of this describes the implementation of the unstable `effects` and ## The `HostEffect` predicate
`const_trait_impl` features. None of this implementation is usable or visible from
stable Rust.
The implementation of const traits and `~const` bounds is a limited effect system. [`HostEffectPredicate`]s are a kind of predicate from `~const Tr` or `const Tr`
It is used to allow trait bounds on `const fn` to be used within the `const fn` for bounds. It has a trait reference, and a `constness` which could be `Maybe` or
method calls. Within the function, in order to know whether a method on a trait `Const` depending on the bound. Because `~const Tr`, or rather `Maybe` bounds
bound is `const`, we need to know whether there is a `~const` bound for the trait. apply differently based on whichever contexts they are in, they have different
In order to know whether we can instantiate a `~const` bound on a `const fn`, we behavior than normal bounds. Where normal trait bounds on a function such as
need to know whether there is a `const_trait` impl for the type and trait being `T: Tr` are collected within the [`predicates_of`] query to be proven when a
used (or whether the `const fn` is used at runtime, then any type implementing the function is called and to be assumed within the function, bounds such as
trait is ok, just like with other bounds). `T: ~const Tr` will behave as a normal trait bound and add `T: Tr` to the result
from `predicates_of`, but also adds a `HostEffectPredicate` to the
[`const_conditions`] query.
We perform these checks via a const generic boolean that gets attached to all On the other hand, `T: const Tr` bounds do not change meaning across contexts,
`const fn` and `const trait`. The following sections will explain the desugarings therefore they will result in `HostEffect(T: Tr, const)` being added to
and the way we perform the checks at call sites. `predicates_of`, and not `const_conditions`.
The const generic boolean is inverted to the meaning of `const`. In the compiler [`HostEffectPredicate`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/predicate/struct.HostEffectPredicate.html
it is called `host`, because it enables "host APIs" like `static` items, network [`predicates_of`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.predicates_of
access, disk access, random numbers and everything else that isn't available in [`const_conditions`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.const_conditions
`const` contexts. So `false` means "const", `true` means "not const" and if it's
a generic parameter, it means "maybe const" (meaning we're in a const fn or const
trait).
## `const fn` ## The `const_conditions` query
All `const fn` have a `#[rustc_host] const host: bool` generic parameter that is `predicates_of` represents a set of predicates that need to be proven to use an
hidden from users. Any `~const Trait` bounds in the generics list or `where` bounds item. For example, to use `foo` in the example below:
of a `const fn` get converted to `Trait<host> + Trait<true>` bounds. The `Trait<true>`
exists so that associated types of the generic param can be used from projections
like `<T as Trait>::Assoc`, because there are no `<T as ~const Trait>` projections for now.
## `#[const_trait] trait`s ```rust
fn foo<T>() where T: Default {}
```
The `#[const_trait]` attribute gives the marked trait a `#[rustc_host] const host: bool` We must be able to prove that `T` implements `Default`. In a similar vein,
generic parameter. All functions of the trait "inherit" this generic parameter, just like `const_conditions` represents a set of predicates that need to be proven to use
they have all the regular generic parameters of the trait. Any `~const Trait` super-trait an item *in const contexts*. If we adjust the example above to use `const` trait
bounds get desugared to `Trait<host> + Trait<true>` in order to allow using associated bounds:
types and consts of the super traits in the trait declaration. This is necessary, because
`<Self as SuperTrait>::Assoc` is always `<Self as SuperTrait<true>>::Assoc` as there is
no `<Self as ~const SuperTrait>` syntax.
## `typeck` performing method and function call checks. ```rust
const fn foo<T>() where T: ~const Default {}
```
When generic parameters are instantiated for any items, the `host` generic parameter Then `foo` would get a `HostEffect(T: Default, maybe)` in the `const_conditions`
is always instantiated as an inference variable. This is a special kind of inference var query, suggesting that in order to call `foo` from const contexts, one must
that is not part of the type or const inference variables, similar to how we have prove that `T` has a const implementation of `Default`.
special inference variables for type variables that we know to be an integer, but not
yet which one. These separate inference variables fall back to `true` at
the end of typeck (in `fallback_effects`) to ensure that `let _ = some_fn_item_name;`
will keep compiling.
All actually used (in function calls, casts, or anywhere else) function items, will ## Enforcement of `const_conditions`
have the `enforce_context_effects` method invoked.
It trivially returns if the function being called has no `host` generic parameter.
In order to error if a non-const function is called in a const context, we have not `const_conditions` are currently checked in various places.
yet disabled the const-check logic that happens on MIR, because
`enforce_context_effects` does not yet perform this check.
The function call's `host` parameter is then equated to the context's `host` value, Every call in HIR from a const context (which includes `const fn` and `const`
which almost always trivially succeeds, as it was an inference var. If the inference items) will check that `const_conditions` of the function we are calling hold.
var has already been bound (since the function item is invoked twice), the second This is done in [`FnCtxt::enforce_context_effects`]. Note that we don't check
invocation checks it against the first. if the function is only referred to but not called, as the following code needs
to compile:
```rust
const fn hi<T: ~const Default>() -> T {
T::default()
}
const X: fn() -> u32 = hi::<u32>;
```
For a trait `impl` to be well-formed, we must be able to prove the
`const_conditions` of the trait from the `impl`'s environment. This is checked
in [`wfcheck::check_impl`].
Here's an example:
```rust
#[const_trait]
trait Bar {}
#[const_trait]
trait Foo: ~const Bar {}
// `const_conditions` contains `HostEffect(Self: Bar, maybe)`
impl const Bar for () {}
impl const Foo for () {}
// ^ here we check `const_conditions` for the impl to be well-formed
```
Methods of trait impls must not have stricter bounds than the method of the
trait that they are implementing. To check that the methods are compatible, a
hybrid environment is constructed with the predicates of the `impl` plus the
predicates of the trait method, and we attempt to prove the predicates of the
impl method. We do the same for `const_conditions`:
```rust
#[const_trait]
trait Foo {
fn hi<T: ~const Default>();
}
impl<T: ~const Clone> Foo for Vec<T> {
fn hi<T: ~const PartialEq>();
// ^ we can't prove `T: ~const PartialEq` given `T: ~const Clone` and
// `T: ~const Default`, therefore we know that the method on the impl
// is stricter than the method on the trait.
}
```
These checks are done in [`compare_method_predicate_entailment`]. A similar
function that does the same check for associated types is called
[`compare_type_predicate_entailment`]. Both of these need to consider
`const_conditions` when in const contexts.
In MIR, as part of const checking, `const_conditions` of items that are called
are revalidated again in [`Checker::revalidate_conditional_constness`].
[`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
[`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
[`FnCtxt::enforce_context_effects`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/fn_ctxt/struct.FnCtxt.html#method.enforce_context_effects
[`wfcheck::check_impl`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_analysis/check/wfcheck/fn.check_impl.html
[`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
## `explicit_implied_const_bounds` on associated types and traits
Bounds on associated types, opaque types, and supertraits such as
```rust
trait Foo: ~const PartialEq {
type X: ~const PartialEq;
}
fn foo() -> impl ~const PartialEq {
// ^ unimplemented syntax
}
```
Have their bounds represented differently. Unlike `const_conditions` which need
to be proved for callers, and can be assumed inside the definition (e.g. trait
bounds on functions), these bounds need to be proved at definition (at the impl,
or when returning the opaque) but can be assumed for callers. The non-const
equivalent of these bounds are called [`explicit_item_bounds`].
These bounds are checked in [`compare_impl_item::check_type_bounds`] for HIR
typeck, [`evaluate_host_effect_from_item_bounds`] in the old solver and
[`consider_additional_alias_assumptions`] in the new solver.
[`explicit_item_bounds`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.explicit_item_bounds
[`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
[`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
[`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
## Proving `HostEffectPredicate`s
`HostEffectPredicate`s are implemented both in the [old solver] and the [new
trait solver]. In general, we can prove a `HostEffect` predicate when either of
these conditions are met:
* The predicate can be assumed from caller bounds;
* The type has a `const` `impl` for the trait, *and* that const conditions on
the impl holds, *and* that the `explicit_implied_const_bounds` on the trait
holds; or
* The type has a built-in implementation for the trait in const contexts. For
example, `Fn` may be implemented by function items if their const conditions
are satisfied, or `Destruct` is implemented in const contexts if the type can
be dropped at compile time.
[old solver]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_trait_selection/traits/effects.rs.html
[new trait solver]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_next_trait_solver/solve/effect_goals.rs.html