-
-
Notifications
You must be signed in to change notification settings - Fork 14.7k
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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, | ||
|
|
@@ -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() => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
| ); | ||
| } | ||
| _ => {} | ||
| }; | ||
|
|
||
|
|
@@ -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 | ||
| /// | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
|
@@ -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
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
This file was deleted.
This file was deleted.
| 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 | ||
|
|
| 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() {} |
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Edit: Actually it's not necessarily blocking since this is only observable under mGCA/GCE? I might just slap
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } |
There was a problem hiding this comment.
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_predicatesandRequiredOutlivesPredicateslol