Promotion (and const-error) master plan

When a CTFE query fails (except with TooGeneric), it should emit a hard error. That would make a lot of code much simpler, some of which having caused soundness problems in the past.

Achieving that goal requires two independent changes:

The former is mostly a matter of "just do it", so the rest of this document is about the latter. For context, please read const-eval#53.

Promotion for required-const args

This is easy, codegen already needs the const value and fails when these promoteds cannot be evaluted, even in dead code. Miri presumably only evaluates them on-demand, but if we could add them to required_consts then that would also be taken care of.

Open question: In const/static initializers, we could in principle be lazy and not evaluate these args unless we have to. But do we want to attempt that? If we do, we are inconsistent with these promoteds in functions, but if we do not, we are likely inconsistent with lifetime extension promoteds in const/static initializers (see below).

Promotion for lifetime extension in fn and const fn

These are codegen'd, so we have to evaluate all promoteds, even in dead code.

fn

Possibly fallible things that we promote:

  • Division, Modulo can we promote them only when the RHS is a non-0 const?
  • Array/slice indexing can we promote only array indexing with a const index (so the bounds check is entirely static)?

Note that other arithmetic is infallible even in debug builds where overflows panic what is actually getting promoted is the CheckedAdd that returns the wrapped result and whether an overflow happens. That operation never fails.

const fn

Accidentally, we promote way more in const fn than in fn. #75502 is exploring if we can sort that out. Ideally, const fn would be treated exactly like fn for the purpose of lifetime extension. If that is not possible it is unclear how much of this plan can be salvaged.

Promotion for lifetime extension in const/static initializers

We currently do a lot of lifetime extension in these contexts (almost every closed expression of the form &expr), and we do it via promotion. It is unlikely that we will be able to do much less lifetime extension (for backwards compatibility reasons).

This would introduce at least two special cases:

  • These would be the only promoteds whose evaluation could fail so to achieve the master plan, we have to make sure we only evaluate them when needed. This means MIR optimizations need to be not run, or very careful. (Some MIR optimizations do linting, but do we even need these lints here?)
  • Inside static mut, we do lifetime extension for things like &mut [1,2,3]. This would be the only promoteds whose result needs to be put into mutable memory. (Right now, this works mostly by accident.) This feature is incompatible with mutable references in const/static bodies.

Unfortunately, the alternative of doing lifetime extension via a different mechanism (StorageDead removal) does not work when there are loops in the initializer.

Select a repo