Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions compiler/rustc_passes/src/reachable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,18 +214,26 @@ impl<'tcx> ReachableContext<'tcx> {
self.visit_const_item_rhs(init);
}
hir::ItemKind::Const(_, _, _, init) => {
// Only things actually ending up in the final constant value are reachable
// for codegen. Everything else is only needed during const-eval, so even if
// const-eval happens in a downstream crate, all they need is
// `mir_for_ctfe`.
if self.tcx.generics_of(item.owner_id).own_requires_monomorphization() {
// In this case, we don't want to evaluate the const initializer.
// In lieu of that, we have to consider everything mentioned in it
// as reachable, since it *may* end up in the final value.
self.visit_const_item_rhs(init);
return;
}

match self.tcx.const_eval_poly_to_alloc(item.owner_id.def_id.into()) {
Ok(alloc) => {
// Only things actually ending up in the final constant value are
// reachable for codegen. Everything else is only needed during
// const-eval, so even if const-eval happens in a downstream crate,
// all they need is `mir_for_ctfe`.
let alloc = self.tcx.global_alloc(alloc.alloc_id).unwrap_memory();
self.propagate_from_alloc(alloc);
}
// We can't figure out which value the constant will evaluate to. In
// lieu of that, we have to consider everything mentioned in the const
// initializer reachable, since it *may* end up in the final value.
// Trivially unsatisfiable bounds on the item prevented us from
// normalizing the initializer. Similar to the other case, we have to
// everything mentioned in it as reachable.
Err(ErrorHandled::TooGeneric(_)) => self.visit_const_item_rhs(init),
// If there was an error evaluating the const, nothing can be reachable
// via it, and anyway compilation will fail.
Expand Down

This file was deleted.

38 changes: 38 additions & 0 deletions tests/codegen-llvm/private-const-fn-only-used-in-const-eval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Check that we — where possible — don't codegen functions that are only used to evaluate
// static / const items, but never used in runtime code.

//@ compile-flags: --crate-type=lib -Copt-level=0

#![feature(generic_const_items)] // only used in the last few test cases

pub static STATIC: () = func0();
const fn func0() {}
// CHECK-NOT: define{{.*}}func0{{.*}}

pub const CONSTANT: () = func1();
const fn func1() {}
// CHECK-NOT: define{{.*}}func1{{.*}}

// We generally don't want to evaluate the initializer of free const items if they have
// non-region params (and even if we did, const eval would fail anyway with "too polymorphic"
// if the initializer actually referenced such a param).
//
// As a result of not being able to look at the final value, during reachability analysis we
// can't tell for sure if for example certain functions end up in the final value or if they're
// only used during const eval. We fall back to a conservative HIR-based approach.

// `func2` isn't needed at runtime but the compiler can't tell for the reason mentioned above.
pub const POLY_CONST_0<const C: bool>: () = func2();
const fn func2() {}
// CHECK: define{{.*}}func2{{.*}}

// `func3` isn't needed at runtime but the compiler can't tell for the reason mentioned above.
pub const POLY_CONST_1<const C: bool>: () = if C { func3() };
const fn func3() {}
// CHECK: define{{.*}}func3{{.*}}

// `func4` *is* needed at runtime (here, the HIR-based approach gets it right).
pub const POLY_CONST_2<const C: bool>: Option<fn() /* or a TAIT */> =
if C { Some(func4) } else { None };
const fn func4() {}
// CHECK: define{{.*}}func4{{.*}}
2 changes: 1 addition & 1 deletion tests/ui/generic-const-items/def-site-eval.fail.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0080]: evaluation panicked: explicit panic
--> $DIR/def-site-eval.rs:13:20
--> $DIR/def-site-eval.rs:32:20
|
LL | const _<'_a>: () = panic!();
| ^^^^^^^^ evaluation of `_` failed here
Expand Down
29 changes: 23 additions & 6 deletions tests/ui/generic-const-items/def-site-eval.rs
Copy link
Copy Markdown
Member Author

@fmease fmease Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going off on a tangent, the whole "don't eval the initializer for non-lifetime-parametric free const items" thing doesn't apply to mGCA's type-level consts which I'm pretty sure is very intentional (IINM) and thus fine, it's also not GCI-specific, e.g., trait T { type const K: usize = const { panic!(); }; } (Self in scope).

I'm only concerned about const items with bodies anyway since the whole idea is to prevent const eval's "bespoke" handling of "too generic" consts "being user observable" / load-bearing for program correctness (the other motivation being consts should behave like fns in this specific scenario) or rephrased "type based" > "value based".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah type const stuff is whatever here. those should be handled by whatever normal:tm: thing we do for wfchecks of type system stuff

Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
//! Test that we only evaluate free const items (their def site to be clear)
//! whose generics don't require monomorphization.
// Test that we don't evaluate the initializer of free const items if they have
// non-region generic parameters (i.e., ones that "require monomorphization").
//
// To peek behind the curtains for a bit, at the time of writing there are three places where we
// usually evaluate the initializer: "analysis", mono item collection & reachability analysis.
// We must ensure that all of them take the generics into account.
//
//@ revisions: fail pass
//@[pass] check-pass

#![feature(generic_const_items)]
#![expect(incomplete_features)]
#![crate_type = "lib"] // (*)

//@ revisions: fail pass
//@[pass] check-pass
// All of these constants are intentionally unused since we want to test the
// behavior at the def site, not at use sites.

const _<_T>: () = panic!();
const _<const _N: usize>: () = panic!();

// Check *public* const items specifically to exercise reachability analysis which normally
// evaluates const initializers to look for function pointers in the final const value.
//
// (*): While reachability analysis also runs for purely binary crates (to find e.g., extern items)
// setting the crate type to library (1) makes the case below 'more realistic' since
// hypothetical downstream crates that require runtime MIR could actually exist.
// (2) It ensures that we exercise the relevant part of the compiler under test.
pub const K<_T>: () = panic!();
pub const Q<const _N: usize>: () = loop {};

#[cfg(fail)]
const _<'_a>: () = panic!(); //[fail]~ ERROR evaluation panicked: explicit panic

fn main() {}
12 changes: 0 additions & 12 deletions tests/ui/generic-const-items/trivially-unsatisfied-bounds-0.rs

This file was deleted.

12 changes: 0 additions & 12 deletions tests/ui/generic-const-items/trivially-unsatisfied-bounds-1.rs

This file was deleted.

11 changes: 0 additions & 11 deletions tests/ui/generic-const-items/trivially-unsatisfied-bounds-1.stderr

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
error[E0080]: entering unreachable code
--> $DIR/trivially-unsatisfied-bounds-0.rs:6:1
--> $DIR/trivially-unsatisfied-bounds.rs:17:1
|
LL | / const UNUSABLE: () = ()
LL | | where
LL | | String: Copy;
| |_________________^ evaluation of `UNUSABLE` failed here
LL | | for<'_delay> String: Copy;
| |______________________________^ evaluation of `UNUSABLE` failed here

error[E0277]: the trait bound `String: Copy` is not satisfied
--> $DIR/trivially-unsatisfied-bounds-0.rs:11:13
--> $DIR/trivially-unsatisfied-bounds.rs:24:13
|
LL | let _ = UNUSABLE;
| ^^^^^^^^ the trait `Copy` is not implemented for `String`
|
note: required by a bound in `UNUSABLE`
--> $DIR/trivially-unsatisfied-bounds-0.rs:8:13
--> $DIR/trivially-unsatisfied-bounds.rs:19:26
|
LL | const UNUSABLE: () = ()
| -------- required by a bound in this constant
LL | where
LL | String: Copy;
| ^^^^ required by this bound in `UNUSABLE`
LL | for<'_delay> String: Copy;
| ^^^^ required by this bound in `UNUSABLE`

error: aborting due to 2 previous errors

Expand Down
33 changes: 33 additions & 0 deletions tests/ui/generic-const-items/trivially-unsatisfied-bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Exercise trivially unsatisfied bounds on free const items.
// Their interaction with the evaluation of the initializer is interesting.
//
//@ revisions: mentioned unmentioned

#![feature(generic_const_items)]

// FIXME(generic_const_items): Try to get rid of error "entering unreachable error", it's
// unnecessary and actually caused by MIR pass `ImpossiblePredicates` replacing the body with the
// terminator `Unreachable` due to the unsatisfied bound which is subsequently reached.
//
// NOTE(#142293): However, don't think about suppressing the evaluation of the initializer if the
// bounds are "impossible". That'd be a SemVer hazard since it could cause downstream to fail to
// compile if upstream added a new trait impl which is undesirable[^1].
// [^1]: Strictly speaking that's already possible due to the one-impl rule.

const UNUSABLE: () = () //~ ERROR entering unreachable code
where
for<'_delay> String: Copy;

fn scope() {
// Ensure that we successfully reject references of consts with trivially unsatisfied bounds.
#[cfg(mentioned)]
let _ = UNUSABLE; //[mentioned]~ ERROR the trait bound `String: Copy` is not satisfied
}

const _BAD: () = <() as Unimplemented>::CT
where
for<'_delay> (): Unimplemented;

trait Unimplemented { const CT: (); }

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error[E0080]: entering unreachable code
--> $DIR/trivially-unsatisfied-bounds.rs:17:1
|
LL | / const UNUSABLE: () = ()
LL | | where
LL | | for<'_delay> String: Copy;
| |______________________________^ evaluation of `UNUSABLE` failed here

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0080`.
Loading