Skip to content

GCI: Imply outlives-bounds on free (generic) const items #143029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
20 changes: 13 additions & 7 deletions compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@ use tracing::debug;
use super::explicit::ExplicitPredicatesMap;
use super::utils::*;

/// Infer predicates for the items in the crate.
///
/// `global_inferred_outlives`: this is initially the empty map that
/// was generated by walking the items in the crate. This will
/// now be filled with inferred predicates.
/// Infer outlives-predicates for the items in the local crate.
pub(super) fn infer_predicates(
tcx: TyCtxt<'_>,
) -> FxIndexMap<DefId, ty::EarlyBinder<'_, RequiredPredicates<'_>>> {
debug!("infer_predicates");

let mut explicit_map = ExplicitPredicatesMap::new();

let mut global_inferred_outlives = FxIndexMap::default();

// If new predicates were added then we need to re-calculate
Expand Down Expand Up @@ -70,6 +65,17 @@ pub(super) fn infer_predicates(
);
}

DefKind::Const => {
insert_required_predicates_to_be_wf(
tcx,
tcx.type_of(item_did).instantiate_identity(),
tcx.def_span(item_did),
&global_inferred_outlives,
&mut item_required_predicates,
&mut explicit_map,
);
}

_ => {}
};

Expand Down Expand Up @@ -312,7 +318,7 @@ fn check_explicit_predicates<'tcx>(
}
}

/// Check the inferred predicates declared on the type.
/// Check the inferred predicates of the type.
///
/// ### Example
///
Expand Down
33 changes: 14 additions & 19 deletions compiler/rustc_hir_analysis/src/outlives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,11 @@ pub(crate) fn provide(providers: &mut Providers) {

fn inferred_outlives_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[(ty::Clause<'_>, Span)] {
match tcx.def_kind(item_def_id) {
DefKind::Struct | DefKind::Enum | DefKind::Union => {
let crate_map = tcx.inferred_outlives_crate(());
crate_map.predicates.get(&item_def_id.to_def_id()).copied().unwrap_or(&[])
}
DefKind::TyAlias if tcx.type_alias_is_lazy(item_def_id) => {
let crate_map = tcx.inferred_outlives_crate(());
crate_map.predicates.get(&item_def_id.to_def_id()).copied().unwrap_or(&[])
}
DefKind::Struct | DefKind::Enum | DefKind::Union | DefKind::Const => {}
DefKind::TyAlias if tcx.type_alias_is_lazy(item_def_id) => {}
DefKind::AnonConst if tcx.features().generic_const_exprs() => {
let id = tcx.local_def_id_to_hir_id(item_def_id);
if tcx.hir_opt_const_param_default_param_def_id(id).is_some() {
return if tcx.hir_opt_const_param_default_param_def_id(id).is_some() {
// In `generics_of` we set the generics' parent to be our parent's parent which means that
// we lose out on the predicates of our actual parent if we dont return those predicates here.
// (See comment in `generics_of` for more information on why the parent shenanigans is necessary)
Expand All @@ -42,21 +36,22 @@ fn inferred_outlives_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[(ty::Clau
tcx.inferred_outlives_of(item_def_id)
} else {
&[]
}
};
}
_ => &[],
_ => return &[],
}

// FIXME(fmease): Explainer. Maybe fast path:
if tcx.generics_of(item_def_id).is_empty() {
return &[];
}

let crate_map = tcx.inferred_outlives_crate(());
crate_map.predicates.get(&item_def_id.to_def_id()).copied().unwrap_or(&[])
}

/// Compute a map from each applicable item in the local crate to its inferred/implied outlives-predicates.
fn inferred_outlives_crate(tcx: TyCtxt<'_>, (): ()) -> CratePredicatesMap<'_> {
// Compute a map from each ADT (struct/enum/union) and lazy type alias to
// the **explicit** outlives predicates (`T: 'a`, `'a: 'b`) that the user wrote.
// Typically there won't be many of these, except in older code where
// they were mandatory. Nonetheless, we have to ensure that every such
// predicate is satisfied, so they form a kind of base set of requirements
// for the type.

// Compute the inferred predicates
let global_inferred_outlives = implicit_infer::infer_predicates(tcx);

// Convert the inferred predicates into the "collected" form the
Expand Down
8 changes: 5 additions & 3 deletions src/doc/rustc-dev-guide/src/traits/implied-bounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ handled... well... implicitly.

## explicit implied bounds

The explicit implied bounds are computed in [`fn inferred_outlives_of`]. Only ADTs and
lazy type aliases have explicit implied bounds which are computed via a fixpoint algorithm
in the [`fn inferred_outlives_crate`] query.
The explicit implied bounds are computed in [`fn inferred_outlives_of`]. Only ADTs,
[lazy type aliases][lta] and [free (generic) const items][gci] have explicit implied bounds
which are computed via a fixpoint algorithm in the [`fn inferred_outlives_crate`] query.

We use [`fn insert_required_predicates_to_be_wf`] on all fields of all ADTs in the crate.
This function computes the outlives bounds for each component of the field using a
Expand All @@ -31,6 +31,8 @@ if the outlived region is a region parameter. [It does not add `'static` require
[`fn check_explicit_predicates`]: https://github.com/rust-lang/rust/blob/5b8bc568d28b2e922290c9a966b3231d0ce9398b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs#L238
[`fn insert_outlives_predicate`]: https://github.com/rust-lang/rust/blob/5b8bc568d28b2e922290c9a966b3231d0ce9398b/compiler/rustc_hir_analysis/src/outlives/utils.rs#L15
[nostatic]: https://github.com/rust-lang/rust/blob/5b8bc568d28b2e922290c9a966b3231d0ce9398b/compiler/rustc_hir_analysis/src/outlives/utils.rs#L159-L165
[lta]: https://github.com/rust-lang/rust/issues/112792
[gci]: https://github.com/rust-lang/rust/issues/113521

## implicit implied bounds

Expand Down
18 changes: 18 additions & 0 deletions tests/ui/generic-const-items/implied-outlives-bounds.neg.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error: lifetime may not live long enough
--> $DIR/implied-outlives-bounds.rs:20:9
|
LL | fn env0<'any>() {
| ---- lifetime `'any` defined here
LL | _ = TYPE_OUTLIVES_0::<'static, &'any ()>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'any` must outlive `'static`

error: lifetime may not live long enough
--> $DIR/implied-outlives-bounds.rs:25:9
|
LL | fn env1<'any>() {
| ---- lifetime `'any` defined here
LL | _ = REGION_OUTLIVES_0::<'static, 'any>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'any` must outlive `'static`

error: aborting due to 2 previous errors

28 changes: 28 additions & 0 deletions tests/ui/generic-const-items/implied-outlives-bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Check that we imply outlives-bounds on free const items.
//@ revisions: pos neg
//@[pos] check-pass
#![feature(generic_const_items)]
#![feature(freeze)] // only used in the test case `TYPE_OUTLIVES_1`
#![expect(incomplete_features)]

const REGION_OUTLIVES_0<'a, 'b>: Option<&'a &'b ()> = None; // we imply `'a: 'b`
const REGION_OUTLIVES_1<'a, 'b>: &'a &'b () = &&(); // we imply `'a: 'b`

const TYPE_OUTLIVES_0<'a, T>: Option<&'a T> = None; // we imply `T: 'a`

const TYPE_OUTLIVES_1<'a, T: Def>: &'a T = &T::DEF; // we imply `T: 'a`
trait Def: std::marker::Freeze { const DEF: Self; }

// Ensure that we actually enforce these implied bounds at usage sites:

#[cfg(neg)]
fn env0<'any>() {
_ = TYPE_OUTLIVES_0::<'static, &'any ()>; //[neg]~ ERROR lifetime may not live long enough
}

#[cfg(neg)]
fn env1<'any>() {
_ = REGION_OUTLIVES_0::<'static, 'any>; //[neg]~ ERROR lifetime may not live long enough
}

fn main() {}
12 changes: 10 additions & 2 deletions tests/ui/generic-const-items/reference-outlives-referent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
// successfully emit a diagnostic. Regression test for issue #114714.

#![feature(generic_const_items)]
#![allow(incomplete_features)]
#![expect(incomplete_features)]

const Q<'a, 'b>: &'a &'b () = &&(); //~ ERROR reference has a longer lifetime than the data it references
struct S<'a>(&'a ());

impl<'a> S<'a> {
const K<'b>: &'a &'b () = &&(); //~ ERROR reference has a longer lifetime than the data it references
}

const Q<'a, 'b>: () = {
let _: &'a &'b () = &&(); //~ ERROR lifetime may not live long enough
};

fn main() {}
30 changes: 21 additions & 9 deletions tests/ui/generic-const-items/reference-outlives-referent.stderr
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
error[E0491]: in type `&'a &'b ()`, reference has a longer lifetime than the data it references
--> $DIR/reference-outlives-referent.rs:7:18
--> $DIR/reference-outlives-referent.rs:10:5
|
LL | const Q<'a, 'b>: &'a &'b () = &&();
| ^^^^^^^^^^
LL | const K<'b>: &'a &'b () = &&();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: the pointer is valid for the lifetime `'a` as defined here
--> $DIR/reference-outlives-referent.rs:7:9
--> $DIR/reference-outlives-referent.rs:9:6
|
LL | const Q<'a, 'b>: &'a &'b () = &&();
| ^^
LL | impl<'a> S<'a> {
| ^^
note: but the referenced data is only valid for the lifetime `'b` as defined here
--> $DIR/reference-outlives-referent.rs:7:13
--> $DIR/reference-outlives-referent.rs:10:13
|
LL | const Q<'a, 'b>: &'a &'b () = &&();
LL | const K<'b>: &'a &'b () = &&();
| ^^

error: aborting due to 1 previous error
error: lifetime may not live long enough
--> $DIR/reference-outlives-referent.rs:14:12
|
LL | const Q<'a, 'b>: () = {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | let _: &'a &'b () = &&();
| ^^^^^^^^^^ requires that `'b` must outlive `'a`
|
= help: consider adding the following bound: `'b: 'a`

error: aborting due to 2 previous errors

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