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
6 changes: 4 additions & 2 deletions compiler/rustc_hir_analysis/src/collect/type_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,10 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_
}
}
}
ItemKind::Const(ident, _, ty, rhs) => {
if ty.is_suggestable_infer_ty() {
ItemKind::Const(ident, generics, ty, rhs) => {
// We can't infer the type of parameterized const items without causing a query
// cycle (typeck -..> inferred_outlives_of -..> type_of -> typeck), so let's not.
if generics.params.is_empty() && ty.is_suggestable_infer_ty() {
infer_placeholder_type(
icx.lowerer(),
def_id,
Expand Down
28 changes: 19 additions & 9 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<'_>>> {
Copy link
Member

Choose a reason for hiding this comment

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

can we rename this stuff to be infer_outlives_predicates and RequiredOutlivesPredicates lol

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 @@ -58,7 +53,6 @@ pub(super) fn infer_predicates(
);
}
}

DefKind::TyAlias if tcx.type_alias_is_lazy(item_did) => {
insert_required_predicates_to_be_wf(
tcx,
Expand All @@ -69,7 +63,23 @@ pub(super) fn infer_predicates(
&mut explicit_map,
);
}

// HACK(generic_const_items): We have to explicitly disqualify const items that do
// not have any parameters to break certain query cycles that would otherwise occur
// when `typeck`'ing const items which we would only do if its type signature was
// ill-formed due to it missing or containing inferred types `_`.
//
// This is correct as we only ever imply outlives-predicates where the outlived
// region is early-bound ... for which the item must have generic parameters.
DefKind::Const { .. } if !tcx.generics_of(item_did).is_empty() => {
Copy link
Member

Choose a reason for hiding this comment

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

I could imagine for mgca wanting to imply bounds from the rhs too. e.g. const FOO<'a>: () = BAR::<'a> where BAR bounds 'a: 'static

Copy link
Member

Choose a reason for hiding this comment

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

where does that perf regression you were talking about actually come from? is it having this query return more stuff? would feature gating this match arm improve things too?

Copy link
Member

Choose a reason for hiding this comment

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

also kind of generally wondering if itd be "safer" to gate this behind GCI to avoid any accidents but that seems.. unlikely to happen

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 +322,7 @@ fn check_explicit_predicates<'tcx>(
}
}

/// Check the inferred predicates declared on the type.
/// Check the inferred predicates of the type.
///
/// ### Example
///
Expand Down
34 changes: 15 additions & 19 deletions compiler/rustc_hir_analysis/src/outlives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,11 @@ pub(super) fn inferred_outlives_of(
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 @@ -40,21 +34,23 @@ pub(super) fn inferred_outlives_of(
tcx.inferred_outlives_of(item_def_id)
} else {
&[]
}
};
}
_ => &[],
_ => return &[],
}

if tcx.generics_of(item_def_id).is_empty() {
// Fast path.
return &[];
}
Comment on lines +42 to 45
Copy link
Member Author

Choose a reason for hiding this comment

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

This fast path allowed me to turn a perf regression (#143029 (comment)) back into neutral state (#143029 (comment)).


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.
pub(super) 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
36 changes: 18 additions & 18 deletions tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.stderr
Original file line number Diff line number Diff line change
@@ -1,21 +1,3 @@
error[E0798]: return value of `"cmse-nonsecure-call"` function too large to pass via registers
--> $DIR/return-via-stack.rs:41:48
|
LL | u128: extern "cmse-nonsecure-call" fn() -> u128,
| ^^^^ this type doesn't fit in the available registers
|
= note: functions with the `"cmse-nonsecure-call"` ABI must pass their result via the available return registers
= note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size

error[E0798]: return value of `"cmse-nonsecure-call"` function too large to pass via registers
--> $DIR/return-via-stack.rs:42:48
|
LL | i128: extern "cmse-nonsecure-call" fn() -> i128,
| ^^^^ this type doesn't fit in the available registers
|
= note: functions with the `"cmse-nonsecure-call"` ABI must pass their result via the available return registers
= note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size

error[E0798]: return value of `"cmse-nonsecure-call"` function too large to pass via registers
--> $DIR/return-via-stack.rs:30:46
|
Expand Down Expand Up @@ -70,6 +52,24 @@ LL | f6: extern "cmse-nonsecure-call" fn() -> [u8; 5],
= note: functions with the `"cmse-nonsecure-call"` ABI must pass their result via the available return registers
= note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size

error[E0798]: return value of `"cmse-nonsecure-call"` function too large to pass via registers
--> $DIR/return-via-stack.rs:41:48
|
LL | u128: extern "cmse-nonsecure-call" fn() -> u128,
| ^^^^ this type doesn't fit in the available registers
|
= note: functions with the `"cmse-nonsecure-call"` ABI must pass their result via the available return registers
= note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size

error[E0798]: return value of `"cmse-nonsecure-call"` function too large to pass via registers
--> $DIR/return-via-stack.rs:42:48
|
LL | i128: extern "cmse-nonsecure-call" fn() -> i128,
| ^^^^ this type doesn't fit in the available registers
|
= note: functions with the `"cmse-nonsecure-call"` ABI must pass their result via the available return registers
= note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size

error[E0798]: return value of `"cmse-nonsecure-call"` function too large to pass via registers
--> $DIR/return-via-stack.rs:57:46
|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ LL | A(u32),
LL ~ B(T),
|

error[E0782]: expected a type, found a trait
--> $DIR/suggest-struct-or-union-add-generic-impl-trait.rs:31:5
|
LL | Trait:, {}
| ^^^^^
|
help: you can add the `dyn` keyword if you want a trait object
|
LL | dyn Trait:, {}
| +++

error[E0782]: expected a type, found a trait
--> $DIR/suggest-struct-or-union-add-generic-impl-trait.rs:35:8
|
Expand All @@ -58,17 +69,6 @@ LL ~ struct HasGenerics<T, U: Trait> {
LL ~ f: U,
|

error[E0782]: expected a type, found a trait
--> $DIR/suggest-struct-or-union-add-generic-impl-trait.rs:31:5
|
LL | Trait:, {}
| ^^^^^
|
help: you can add the `dyn` keyword if you want a trait object
|
LL | dyn Trait:, {}
| +++

error: aborting due to 6 previous errors

For more information about this error, try `rustc --explain E0782`.
20 changes: 0 additions & 20 deletions tests/ui/generic-const-items/assoc-const-missing-type.rs

This file was deleted.

36 changes: 0 additions & 36 deletions tests/ui/generic-const-items/assoc-const-missing-type.stderr

This file was deleted.

18 changes: 18 additions & 0 deletions tests/ui/generic-const-items/implied-outlives-bounds-0.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-0.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-0.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-0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Check that we imply outlives-bounds on (generic) 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() {}
26 changes: 26 additions & 0 deletions tests/ui/generic-const-items/implied-outlives-bounds-1.rs
Copy link
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.

Right, I completely forgot about implementing that part of implied bounds back in June 2025. This PR should definitely be blocked on me implementing that but feel free to review the other parts already.

It should hopefully be straightforward to implement modulo perf, I just need to visit & handle ConstKind::Unevaluated in insert_outlives_predicate.

Edit: Actually it's not necessarily blocking since this is only observable under mGCA/GCE? I might just slap //@ known-bug: unknown on it 🤷

Copy link
Member

Choose a reason for hiding this comment

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

i think not even GCE as there you'll have anon consts with the same predicates as the item its inside of. mGCA should be the only way to get a const item directly used in a type signature which has clauses that are actual useful to derive implied bounds from

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// FIXME(fmease): This still needs to be implemented
//@ ignore-test
// Check that paths referring to (generic) free const items induce implied bounds.
//@ revisions: pos neg
//@[pos] check-pass
#![feature(generic_const_items, min_generic_const_args)]
#![expect(incomplete_items)]

// References of `EXP::<'r, 'q>` should induce implied bound `'q: 'r` in the enclosing def.
type const EXP<'a, 'b: 'a>: usize = 0;

struct Ty<'a, 'b>([(); EXP::<'a, 'b>]); // we imply `'a: 'b`
const CT<'a, 'b>: [(); EXP::<'a, 'b>] = {
let _: &'a &'b (); // OK since `EXP::<'a, 'b>` implies `'a: 'b`
[]
};

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

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