From c42c65761cba6eb8e97fe7708963c6b197dbefb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 8 Oct 2026 19:26:22 +0200 Subject: [PATCH 1/6] Clean up code related to inferred outlives-clauses In summary, rename various things & update a lot of comments. 1. Rename some types, fns that have "clause" in their name to contain "outlives clause" instead. While we are in a module called `outlives`, it's not immediately obvious & it certainly doesn't imply that all of these things only represent / act on outlives-clauses. Requested by Boxy. 2. Both `global_inferred_outlives` and `explicit_map` store a "global" mapping from each crate-local item to a set of outlives-clauses collected so far (one containing the inferred ones, the other the explicit ones). So name both consistently: `global_inferred_clauses` and `global_explicit_clauses`, respectively. Similarly for their types which are now prefixed with `Global`. 3. Remove a bunch of really obvious & distracting comments. 4. Reduce rightward drift in the `ty::Dynamic` match arm by using an if-let guard. 5. Deduplicate code shared between ADTs & CTAs in `inferred_outlives_of` 6. `bug!()` in a `GenericArgKind::Const(_)` branch which we know is currently unreachable and which should explode if that were to change. 7. Rename `CrateClausesMap` to `CrateInferredOutlivesClausesMap`. The type is public & lives in `rustc_middle` since `inferred_outlives_crate` is a query. Considering that, its name is way too general. Moreover, remove all of its docs. This type can only be constructed via query `inferred_outlives_crate` (well, and via the provider) which has good docs already that also warn about the fact that query `inferred_outlives_of` should always be used instead. 8. Remove comment "Typically there won't be many of these [explicit outlives clauses], except in older code where they were mandatory" is unnecessary and factually incorrect since there are various places where we don't imply (early-bound) outlives-bounds. 9. Re. comment "Visit all the crates [...]" and "[...] we walk the crates again [...]" is no longer correct: We only (re)visit the items in the local crate; the inferred outlives clauses for cross-crate items have already been computed and stored in the relevant crate metadata files. 10. Fix the example given in `check_explicit_clauses`'s docs, it has several issues: Struct `Outer` gets used as a trait bound, it uses `...: 'static` bounds and states they get processed but as a matter of fact we don't process static bounds, we specifically only infer clauses where the LHS is early-bound! 11. Various minor code tweaks. --- .../src/outlives/explicit.rs | 30 +-- .../src/outlives/implicit_infer.rs | 227 +++++++----------- .../rustc_hir_analysis/src/outlives/mod.rs | 128 +++++----- .../rustc_hir_analysis/src/outlives/utils.rs | 17 +- compiler/rustc_middle/src/queries.rs | 2 +- compiler/rustc_middle/src/ty/mod.rs | 11 +- 6 files changed, 171 insertions(+), 244 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/outlives/explicit.rs b/compiler/rustc_hir_analysis/src/outlives/explicit.rs index ffbbb316e5c1f..ce266ce0b2952 100644 --- a/compiler/rustc_hir_analysis/src/outlives/explicit.rs +++ b/compiler/rustc_hir_analysis/src/outlives/explicit.rs @@ -1,41 +1,35 @@ -use rustc_data_structures::fx::FxIndexMap; use rustc_hir::def_id::DefId; use rustc_middle::ty::{self, OutlivesClause, TyCtxt}; use super::utils::*; -#[derive(Debug)] -pub(crate) struct ExplicitClausesMap<'tcx> { - map: FxIndexMap>>, +#[derive(Default, Debug)] +pub(crate) struct GlobalExplicitOutlivesClauses<'tcx> { + map: GlobalOutlivesClauses<'tcx>, } -impl<'tcx> ExplicitClausesMap<'tcx> { - pub(crate) fn new() -> ExplicitClausesMap<'tcx> { - ExplicitClausesMap { map: FxIndexMap::default() } - } - - pub(crate) fn explicit_clauses_of( +impl<'tcx> GlobalExplicitOutlivesClauses<'tcx> { + pub(crate) fn explicit_outlives_clauses_of( &mut self, tcx: TyCtxt<'tcx>, def_id: DefId, - ) -> &ty::EarlyBinder<'tcx, RequiredClauses<'tcx>> { + ) -> &ty::EarlyBinder<'tcx, OutlivesClauses<'tcx>> { self.map.entry(def_id).or_insert_with(|| { - let gen_clauses = if def_id.is_local() { + let clauses = if def_id.is_local() { tcx.explicit_clauses_of(def_id) } else { tcx.clauses_of(def_id) }; - let mut required_clauses = RequiredClauses::default(); + let mut outlives_clauses = OutlivesClauses::default(); - // Process clauses and convert to `RequiredClauses` entry, see below. - for &(clause, span) in gen_clauses.clauses { + for &(clause, span) in clauses.clauses { match clause.kind().skip_binder() { ty::ClauseKind::TypeOutlives(OutlivesClause(ty, reg)) => { - insert_outlives_clause(tcx, ty.into(), reg, span, &mut required_clauses) + insert_outlives_clause(tcx, ty.into(), reg, span, &mut outlives_clauses) } ty::ClauseKind::RegionOutlives(OutlivesClause(reg1, reg2)) => { - insert_outlives_clause(tcx, reg1.into(), reg2, span, &mut required_clauses) + insert_outlives_clause(tcx, reg1.into(), reg2, span, &mut outlives_clauses) } ty::ClauseKind::Trait(_) | ty::ClauseKind::Projection(_) @@ -47,7 +41,7 @@ impl<'tcx> ExplicitClausesMap<'tcx> { } } - ty::EarlyBinder::bind_iter(required_clauses) + ty::EarlyBinder::bind_iter(outlives_clauses) }) } } diff --git a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs index ca7e246a9e05b..0e5e1ff7ab383 100644 --- a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs +++ b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs @@ -1,124 +1,105 @@ -use rustc_data_structures::fx::FxIndexMap; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_middle::ty::{self, GenericArg, GenericArgKind, Ty, TyCtxt}; use rustc_span::Span; use tracing::debug; -use super::explicit::ExplicitClausesMap; +use super::explicit::GlobalExplicitOutlivesClauses; use super::utils::*; /// Infer outlives-clauses for the items in the local crate. -pub(super) fn infer_clauses( - tcx: TyCtxt<'_>, -) -> FxIndexMap>> { - debug!("infer_clauses"); +#[tracing::instrument(level = "debug", skip_all)] +pub(super) fn infer_outlives_clauses(tcx: TyCtxt<'_>) -> GlobalOutlivesClauses<'_> { + let mut global_explicit_clauses = GlobalExplicitOutlivesClauses::default(); + let mut global_inferred_clauses = GlobalOutlivesClauses::default(); - let mut explicit_map = ExplicitClausesMap::new(); - - let mut global_inferred_outlives = FxIndexMap::default(); - - // If new clauses were added then we need to re-calculate - // all crates since there could be new implied clauses. for i in 0.. { - let mut clauses_added = vec![]; + // Whenever new clauses get added we need to re-calculate the set of + // clauses for all items since there could be new implied clauses. + let mut items_with_added_clauses = vec![]; - // Visit all the crates and infer clauses + // Visit all free items in the local crate and infer outlives-clauses. + // We simply don't consider other kinds of items at the time of writing. for id in tcx.hir_free_items() { let item_did = id.owner_id; + debug!(?item_did); - debug!("InferVisitor::visit_item(item={:?})", item_did); - - let mut item_required_clauses = RequiredClauses::default(); + let mut item_required_clauses = OutlivesClauses::default(); match tcx.def_kind(item_did) { DefKind::Union | DefKind::Enum | DefKind::Struct => { let adt_def = tcx.adt_def(item_did.to_def_id()); - // Iterate over all fields in item_did for field_def in adt_def.all_fields() { - // Calculating the clause requirements necessary - // for item_did. - // - // For field of type &'a T (reference) or Adt - // (struct/enum/union) there will be outlive - // requirements for adt_def. - let field_ty = - tcx.type_of(field_def.did).instantiate_identity().skip_norm_wip(); - let field_span = tcx.def_span(field_def.did); - insert_required_clauses_to_be_wf( + insert_required_outlives_clauses_to_be_wf( tcx, - field_ty, - field_span, - &global_inferred_outlives, + tcx.type_of(field_def.did).instantiate_identity().skip_norm_wip(), + tcx.def_span(field_def.did), &mut item_required_clauses, - &mut explicit_map, + &global_inferred_clauses, + &mut global_explicit_clauses, ); } } - DefKind::TyAlias if tcx.type_alias_is_checked(item_did) => { - insert_required_clauses_to_be_wf( + insert_required_outlives_clauses_to_be_wf( tcx, tcx.type_of(item_did).instantiate_identity().skip_norm_wip(), tcx.def_span(item_did), - &global_inferred_outlives, &mut item_required_clauses, - &mut explicit_map, + &global_inferred_clauses, + &mut global_explicit_clauses, ); } - _ => {} }; - // If new clauses were added (`local_clause_map` has more - // clauses than the `global_inferred_outlives`), the new clauses - // might result in implied clauses for their parent types. - // Therefore mark `clauses_added` as true and which will ensure - // we walk the crates again and re-calculate clauses for all - // items. - let item_clauses_len: usize = global_inferred_outlives + // If there are more required clauses than inferred ones, it means new clauses were + // added which might result in implied clauses for their overarching types. + // Register the item to ensure we visit all items again and re-calculate clauses. + let item_inferred_clauses: usize = global_inferred_clauses .get(&item_did.to_def_id()) .map_or(0, |c| c.as_ref().skip_binder().len()); - if item_required_clauses.len() > item_clauses_len { - clauses_added.push(item_did); - global_inferred_outlives.insert( + if item_required_clauses.len() > item_inferred_clauses { + items_with_added_clauses.push(item_did); + global_inferred_clauses.insert( item_did.to_def_id(), ty::EarlyBinder::bind_iter(item_required_clauses), ); } } - if clauses_added.is_empty() { + if items_with_added_clauses.is_empty() { // We've reached a fixed point. break; - } else if !tcx.recursion_limit().value_within_limit(i) { - let msg = if let &[id] = &clauses_added[..] { + } + + if !tcx.recursion_limit().value_within_limit(i) { + let msg = if let &[id] = &items_with_added_clauses[..] { format!("overflow computing implied lifetime bounds for `{}`", tcx.def_path_str(id),) } else { "overflow computing implied lifetime bounds".to_string() }; tcx.dcx().span_fatal( - clauses_added.iter().map(|id| tcx.def_span(*id)).collect::>(), + items_with_added_clauses.iter().map(|id| tcx.def_span(*id)).collect::>(), msg, ); } } - global_inferred_outlives + global_inferred_clauses } -fn insert_required_clauses_to_be_wf<'tcx>( +fn insert_required_outlives_clauses_to_be_wf<'tcx>( tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, - global_inferred_outlives: &FxIndexMap>>, - required_clauses: &mut RequiredClauses<'tcx>, - explicit_map: &mut ExplicitClausesMap<'tcx>, + required_clauses: &mut OutlivesClauses<'tcx>, + global_inferred_clauses: &GlobalOutlivesClauses<'tcx>, + global_explicit_clauses: &mut GlobalExplicitOutlivesClauses<'tcx>, ) { for arg in ty.walk() { let leaf_ty = match arg.kind() { GenericArgKind::Type(ty) => ty, - // No clauses from lifetimes or constants, except potentially // constants' types, but `walk` will get to them as well. GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue, @@ -126,22 +107,16 @@ fn insert_required_clauses_to_be_wf<'tcx>( match *leaf_ty.kind() { ty::Ref(region, rty, _) => { - // The type is `&'a T` which means that we will have - // a clause requirement of `T: 'a` (`T` outlives `'a`). - // - // We also want to calculate potential clauses for the `T`. - debug!("Ref"); + // The type `&'a T` inherently has the requirement `T: 'a`. insert_outlives_clause(tcx, rty.into(), region, span, required_clauses); } ty::Adt(def, args) => { - // For ADTs (structs/enums/unions), we check inferred and explicit clauses. - debug!("Adt"); check_inferred_clauses( tcx, def.did(), args, - global_inferred_outlives, + global_inferred_clauses, required_clauses, ); check_explicit_clauses( @@ -149,20 +124,17 @@ fn insert_required_clauses_to_be_wf<'tcx>( def.did(), args, required_clauses, - explicit_map, + global_explicit_clauses, IgnoreClausesReferencingSelf::No, ); } ty::Alias(_, ty::AliasTy { kind: ty::Free { def_id }, args, .. }) => { - // This corresponds to a type like `Type<'a, T>`. - // We check inferred and explicit clauses. - debug!("Free"); check_inferred_clauses( tcx, def_id, args, - global_inferred_outlives, + global_inferred_clauses, required_clauses, ); check_explicit_clauses( @@ -170,51 +142,47 @@ fn insert_required_clauses_to_be_wf<'tcx>( def_id, args, required_clauses, - explicit_map, + global_explicit_clauses, IgnoreClausesReferencingSelf::No, ); } - ty::Dynamic(obj, ..) => { - // This corresponds to `dyn Trait<..>`. In this case, we should - // use the explicit clauses as well. - debug!("Dynamic"); - if let Some(trait_ref) = obj.principal() { - let args = trait_ref - .with_self_ty(tcx, tcx.types.trait_object_dummy_self) - .skip_binder() - .args; - // We skip clauses that reference the `Self` type parameter since we don't - // want to leak the dummy Self to the clauses map. - // - // While filtering out bounds like `Self: 'a` as in `trait Trait<'a, T>: 'a {}` - // doesn't matter since they can't affect the lifetime / type parameters anyway, - // for bounds like `Self::AssocTy: 'b` which we of course currently also ignore - // (see also #54467) it might conceivably be better to extract the binding - // `AssocTy = U` from the trait object type (which must exist) and thus infer - // an outlives requirement that `U: 'b`. - check_explicit_clauses( - tcx, - trait_ref.def_id(), - args, - required_clauses, - explicit_map, - IgnoreClausesReferencingSelf::Yes, - ); - } + ty::Dynamic(obj, ..) if let Some(principal_trait_ref) = obj.principal() => { + let args = principal_trait_ref + .with_self_ty(tcx, tcx.types.trait_object_dummy_self) + .skip_binder() + .args; + // We skip clauses that reference the `Self` type parameter since we don't + // want to leak the dummy Self to the clauses map. + // + // While filtering out bounds like `Self: 'a` as in `trait Trait<'a, T>: 'a {}` + // doesn't matter since they can't affect the lifetime / type parameters anyway, + // for bounds like `Self::AssocTy: 'b` which we of course currently also ignore + // (see also #54467) it might conceivably be better to extract the binding + // `AssocTy = U` from the trait object type (which must exist) and thus infer + // an outlives requirement that `U: 'b`. + check_explicit_clauses( + tcx, + principal_trait_ref.def_id(), + args, + required_clauses, + global_explicit_clauses, + IgnoreClausesReferencingSelf::Yes, + ); } ty::Alias(_, ty::AliasTy { kind: ty::Projection { def_id }, args, .. }) => { - // This corresponds to a type like `<() as Trait<'a, T>>::Type`. - // We only use the explicit clauses of the trait but - // not the ones of the associated type itself. - debug!("Projection"); + // We only use the explicit clauses of the trait but not the ones of the associated + // type itself. FIXME(#141692): Ideally we would consider them, too, though. + // + // E.g., for `<() as Trait<'b, U>>::Type` we will infer `U: 'b` (modulo component + // splitting) given `trait Trait<'a, T: 'a> { type Type; }` if `'b` is early-bound. check_explicit_clauses( tcx, tcx.parent(def_id), args, required_clauses, - explicit_map, + global_explicit_clauses, IgnoreClausesReferencingSelf::No, ); } @@ -233,42 +201,40 @@ fn insert_required_clauses_to_be_wf<'tcx>( /// /// ```ignore (illustrative) /// struct Outer<'a, T> { -/// field: Inner, +/// outer: Inner<'a, T>, /// } /// -/// struct Inner where U: 'static, U: Outer { -/// // ... +/// struct Inner<'b, U: Trait + 'b> { +/// inner: U, +/// _marker: &'b (), /// } /// ``` -/// Here, we should fetch the explicit clauses, which -/// will give us `U: 'static` and `U: Outer`. The latter we -/// can ignore, but we will want to process `U: 'static`, -/// applying the instantiation as above. +/// +/// Here, when processing the type of field `outer`, namely ADT `Inner<'a, T>`, we fetch the set of +/// explicit clauses which gives us `U: Trait` and `U: 'b`. We ignore the former and instantiate +/// the latter using `['b => 'a, U => T]` to obtain the requirement that `T: 'a` holds which we then +/// insert into the set of `required_clauses` (which belongs to `Outer`). #[tracing::instrument(level = "debug", skip(tcx))] fn check_explicit_clauses<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, args: &[GenericArg<'tcx>], - required_clauses: &mut RequiredClauses<'tcx>, - explicit_map: &mut ExplicitClausesMap<'tcx>, + required_clauses: &mut OutlivesClauses<'tcx>, + global_explicit_clauses: &mut GlobalExplicitOutlivesClauses<'tcx>, ignore_clauses_refing_self: IgnoreClausesReferencingSelf, ) { - let explicit_clauses = explicit_map.explicit_clauses_of(tcx, def_id); + let explicit_clauses = global_explicit_clauses.explicit_outlives_clauses_of(tcx, def_id); for (&clause @ ty::OutlivesClause(arg, _), &span) in explicit_clauses.as_ref().skip_binder() { - debug!(?clause); - if let IgnoreClausesReferencingSelf::Yes = ignore_clauses_refing_self && arg.walk().any(|arg| arg == tcx.types.self_param.into()) { - debug!("ignoring clause since it references `Self`"); + debug!("ignoring clause `{clause}` since it references `Self`"); continue; } - let clause @ ty::OutlivesClause(arg, region) = + let ty::OutlivesClause(arg, region) = explicit_clauses.rebind(clause).instantiate(tcx, args).skip_norm_wip(); - debug!(?clause); - insert_outlives_clause(tcx, arg, region, span, required_clauses); } } @@ -293,28 +259,21 @@ enum IgnoreClausesReferencingSelf { /// } /// ``` /// -/// Here, when processing the type of field `outer`, we would request the -/// set of implicit clauses computed for `Inner` thus far. This will -/// initially come back empty, but in next round we will get `U: 'b`. -/// We then apply the instantiation `['b => 'a, U => T]` and thus get the -/// requirement that `T: 'a` holds for `Outer`. +/// Here, when processing the type of field `outer`, namely ADT `Inner<'a, T>`, we request the set +/// of implicit clauses computed for `Inner` thus far. Initially it comes back empty but in the next +/// round we get `U: 'b`. We then apply the instantiation `['b => 'a, U => T]` and thus get the +/// requirement that `T: 'a` holds which we then insert into the set of `required_clauses` (which +/// belongs to `Outer`). fn check_inferred_clauses<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, args: ty::GenericArgsRef<'tcx>, - global_inferred_outlives: &FxIndexMap>>, - required_clauses: &mut RequiredClauses<'tcx>, + global_inferred_clauses: &GlobalOutlivesClauses<'tcx>, + required_clauses: &mut OutlivesClauses<'tcx>, ) { - // Load the current set of inferred and explicit clauses from `global_inferred_outlives` - // and filter the ones that are `TypeOutlives`. - - let Some(clauses) = global_inferred_outlives.get(&def_id) else { - return; - }; + let Some(clauses) = global_inferred_clauses.get(&def_id) else { return }; for (&clause, &span) in clauses.as_ref().skip_binder() { - // `clause` is `U: 'b` in the example above. - // So apply the instantiation to get `T: 'a`. let ty::OutlivesClause(arg, region) = clauses.rebind(clause).instantiate(tcx, args).skip_norm_wip(); insert_outlives_clause(tcx, arg, region, span, required_clauses); diff --git a/compiler/rustc_hir_analysis/src/outlives/mod.rs b/compiler/rustc_hir_analysis/src/outlives/mod.rs index 1db58bf063963..33572ccb960be 100644 --- a/compiler/rustc_hir_analysis/src/outlives/mod.rs +++ b/compiler/rustc_hir_analysis/src/outlives/mod.rs @@ -1,7 +1,7 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; -use rustc_middle::ty::{self, CrateClausesMap, GenericArgKind, TyCtxt, Upcast}; -use rustc_span::Span; +use rustc_middle::ty::{self, CrateInferredOutlivesClausesMap, GenericArgKind, TyCtxt, Upcast}; +use rustc_span::{Span, bug}; pub(crate) mod dump; mod explicit; @@ -13,84 +13,66 @@ 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.clauses.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]) + DefKind::Struct | DefKind::Enum | DefKind::Union => {} + DefKind::TyAlias if tcx.type_alias_is_checked(item_def_id) => {} + DefKind::AnonConst + if tcx.features().generic_const_exprs() + && let id = tcx.local_def_id_to_hir_id(item_def_id) + && 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 clauses of our actual parent if we dont return those clauses here. + // (See comment in `generics_of` for more information on why the parent shenanigans is necessary) + // + // struct Foo<'a, 'b, const N: usize = { ... }>(&'a &'b ()); + // ^^^ ^^^^^^^ the def id we are calling + // ^^^ inferred_outlives_of on + // parent item we dont have set as the + // parent of generics returned by `generics_of` + // + // In the above code we want the anon const to have clauses in its param env for `'b: 'a` + let item_def_id = tcx.hir_get_parent_item(id); + // In the above code example we would be calling `inferred_outlives_of(Foo)` here + return tcx.inferred_outlives_of(item_def_id); } - DefKind::TyAlias if tcx.type_alias_is_checked(item_def_id) => { - let crate_map = tcx.inferred_outlives_crate(()); - crate_map.clauses.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]) - } - 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() { - // In `generics_of` we set the generics' parent to be our parent's parent which means that - // we lose out on the clauses of our actual parent if we dont return those clauses here. - // (See comment in `generics_of` for more information on why the parent shenanigans is necessary) - // - // struct Foo<'a, 'b, const N: usize = { ... }>(&'a &'b ()); - // ^^^ ^^^^^^^ the def id we are calling - // ^^^ inferred_outlives_of on - // parent item we dont have set as the - // parent of generics returned by `generics_of` - // - // In the above code we want the anon const to have clauses in its param env for `'b: 'a` - let item_def_id = tcx.hir_get_parent_item(id); - // In the above code example we would be calling `inferred_outlives_of(Foo)` here - tcx.inferred_outlives_of(item_def_id) - } else { - &[] - } - } - _ => &[], + _ => return &[], } -} -pub(super) fn inferred_outlives_crate(tcx: TyCtxt<'_>, (): ()) -> CrateClausesMap<'_> { - // Compute a map from each ADT (struct/enum/union) and checked type alias to - // the **explicit** outlives clauses (`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 - // clause is satisfied, so they form a kind of base set of requirements - // for the type. + let crate_map = tcx.inferred_outlives_crate(()); + crate_map.clauses.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]) +} - // Compute the inferred clauses - let global_inferred_outlives = implicit_infer::infer_clauses(tcx); +/// Compute a map from each applicable item in the local crate to its +/// inferred / implied outlives-clauses. +pub(super) fn inferred_outlives_crate( + tcx: TyCtxt<'_>, + (): (), +) -> CrateInferredOutlivesClausesMap<'_> { + let global_inferred_clauses = implicit_infer::infer_outlives_clauses(tcx); - // Convert the inferred clauses into the "collected" form the - // global data structure expects. - // - // FIXME -- consider correcting impedance mismatch in some way, - // probably by updating the global data structure. - let clauses = global_inferred_outlives + // Convert the inferred clauses into a form the global data structure expects. + // FIXME: Consider correcting impedance mismatch in some way, + // probably by updating the global data structure. + let clauses = global_inferred_clauses .iter() - .map(|(&def_id, set)| { - let clauses = - &*tcx.arena.alloc_from_iter(set.as_ref().skip_binder().iter().filter_map( - |(ty::OutlivesClause(arg1, region2), &span)| { - match arg1.kind() { - GenericArgKind::Type(ty1) => Some(( - ty::ClauseKind::TypeOutlives(ty::OutlivesClause(ty1, *region2)) - .upcast(tcx), - span, - )), - GenericArgKind::Lifetime(region1) => Some(( - ty::ClauseKind::RegionOutlives(ty::OutlivesClause( - region1, *region2, - )) - .upcast(tcx), - span, - )), - GenericArgKind::Const(_) => { - // Generic consts don't impose any constraints. - None - } - } - }, - )); - (def_id, clauses) + .map(|(&def_id, clauses)| { + let clauses = clauses.as_ref().skip_binder().iter().filter_map( + |(&ty::OutlivesClause(arg1, region2), &span)| match arg1.kind() { + GenericArgKind::Type(ty1) => Some(( + ty::ClauseKind::TypeOutlives(ty::OutlivesClause(ty1, region2)).upcast(tcx), + span, + )), + GenericArgKind::Lifetime(region1) => Some(( + ty::ClauseKind::RegionOutlives(ty::OutlivesClause(region1, region2)) + .upcast(tcx), + span, + )), + GenericArgKind::Const(_) => bug!(), + }, + ); + (def_id, &*tcx.arena.alloc_from_iter(clauses)) }) .collect(); - ty::CrateClausesMap { clauses } + ty::CrateInferredOutlivesClausesMap { clauses } } diff --git a/compiler/rustc_hir_analysis/src/outlives/utils.rs b/compiler/rustc_hir_analysis/src/outlives/utils.rs index e561fabff0564..23665fb6d808b 100644 --- a/compiler/rustc_hir_analysis/src/outlives/utils.rs +++ b/compiler/rustc_hir_analysis/src/outlives/utils.rs @@ -1,12 +1,14 @@ use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::def_id::DefId; use rustc_middle::ty::outlives::{Component, push_outlives_components}; use rustc_middle::ty::{self, GenericArg, GenericArgKind, Region, Ty, TyCtxt}; use rustc_span::{Span, bug, span_bug}; use smallvec::smallvec; -/// Tracks the `T: 'a` or `'a: 'a` clauses that we have inferred -/// must be added to the struct header. -pub(crate) type RequiredClauses<'tcx> = FxIndexMap, Span>; +pub(crate) type OutlivesClauses<'tcx> = FxIndexMap, Span>; + +pub(crate) type GlobalOutlivesClauses<'tcx> = + FxIndexMap>>; /// Given a requirement `T: 'a` or `'b: 'a`, deduce the /// outlives_component and add it to `required_clauses` @@ -15,7 +17,7 @@ pub(crate) fn insert_outlives_clause<'tcx>( arg: GenericArg<'tcx>, outlived_region: Region<'tcx>, span: Span, - required_clauses: &mut RequiredClauses<'tcx>, + required_clauses: &mut OutlivesClauses<'tcx>, ) { // If the `'a` region is bound within the field type itself, we // don't want to propagate this constraint to the header. @@ -136,9 +138,8 @@ pub(crate) fn insert_outlives_clause<'tcx>( required_clauses.entry(ty::OutlivesClause(arg, outlived_region)).or_insert(span); } - GenericArgKind::Const(_) => { - // Generic consts don't impose any constraints. - } + // Const args cannot be constrained via outlives-clauses. + GenericArgKind::Const(_) => {} } } @@ -169,7 +170,7 @@ fn is_free_region(region: Region<'_>) -> bool { // } // // The type above might generate a `T: 'b` bound, but we can - // ignore it. We can't name this lifetime pn the struct header anyway. + // ignore it. We can't name this lifetime in the struct header anyway. ty::ReBound(..) => false, ty::ReError(_) => false, diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 56889f84f38d6..2e7d0b8ff2e76 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -1060,7 +1060,7 @@ rustc_queries! { /// **Do not call this query** directly, use [`Self::inferred_outlives_of`] instead. /// /// - query inferred_outlives_crate(_: ()) -> &'tcx ty::CrateClausesMap<'tcx> { + query inferred_outlives_crate(_: ()) -> &'tcx ty::CrateInferredOutlivesClausesMap<'tcx> { arena_cache desc { "computing the inferred outlives-clauses for items in this crate" } } diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index e6321fadd0701..862a096a466c5 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -534,17 +534,8 @@ impl<'tcx> rustc_type_ir::Flags for Ty<'tcx> { } } -/// The crate outlives map is computed during typeck and contains the -/// outlives of every item in the local crate. You should not use it -/// directly, because to do so will make your pass dependent on the -/// HIR of every item in the local crate. Instead, use -/// `tcx.inferred_outlives_of()` to get the outlives for a *particular* -/// item. #[derive(StableHash, Debug)] -pub struct CrateClausesMap<'tcx> { - /// For each struct with outlive bounds, maps to a vector of the - /// clause of its outlive bounds. If an item has no outlives - /// bounds, it will have no entry. +pub struct CrateInferredOutlivesClausesMap<'tcx> { pub clauses: DefIdMap<&'tcx [(Clause<'tcx>, Span)]>, } From d2bf7e057a1843fbeab83057155a4b5c5e0d48ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 18 Oct 2026 12:04:28 +0200 Subject: [PATCH 2/6] --------------- BRANCH SEPARATOR --------------- From 5f165c0b95ee99f6985fb8f70cd5329be2b61ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 11 Jun 2025 13:25:21 +0200 Subject: [PATCH 3/6] GCI: Imply outlives-bounds on free (generic) const items from their type --- .../src/attributes/rustc_dump.rs | 1 + .../rustc_hir_analysis/src/collect/type_of.rs | 6 ++- .../src/outlives/implicit_infer.rs | 18 +++++++ .../rustc_hir_analysis/src/outlives/mod.rs | 2 +- .../src/traits/implied-bounds.md | 4 +- .../assoc-const-missing-type.rs | 20 ------- .../assoc-const-missing-type.stderr | 37 ------------- .../implied-outlives-bounds-0.dump.stderr | 34 ++++++++++++ .../implied-outlives-bounds-0.fail.stderr | 18 +++++++ .../implied-outlives-bounds-0.rs | 42 +++++++++++++++ .../missing-or-inferred-type.rs | 39 ++++++++++++++ .../missing-or-inferred-type.stderr | 54 +++++++++++++++++++ .../reference-outlives-referent.rs | 13 ++++- .../reference-outlives-referent.stderr | 47 ++++++++++++---- 14 files changed, 263 insertions(+), 72 deletions(-) delete mode 100644 tests/ui/generic-const-items/assoc-const-missing-type.rs delete mode 100644 tests/ui/generic-const-items/assoc-const-missing-type.stderr create mode 100644 tests/ui/generic-const-items/implied-outlives-bounds-0.dump.stderr create mode 100644 tests/ui/generic-const-items/implied-outlives-bounds-0.fail.stderr create mode 100644 tests/ui/generic-const-items/implied-outlives-bounds-0.rs create mode 100644 tests/ui/generic-const-items/missing-or-inferred-type.rs create mode 100644 tests/ui/generic-const-items/missing-or-inferred-type.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs index 07ce47b8858e2..19a1381f002f0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_dump.rs @@ -96,6 +96,7 @@ impl NoArgsAttributeParser for RustcDumpInferredOutlivesParser { Allow(Target::Enum), Allow(Target::Union), Allow(Target::TyAlias), + Allow(Target::Const), ]); const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcDumpInferredOutlives; diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs index 4beefa905a134..9b97e0f8a6ce6 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs @@ -172,8 +172,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() { let hir_body_id = match rhs { ConstItemRhs::Body(body) => Some(body.hir_id), ConstItemRhs::Direct(_) => None, diff --git a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs index 0e5e1ff7ab383..6cd66efe077dc 100644 --- a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs +++ b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs @@ -50,6 +50,24 @@ pub(super) fn infer_outlives_clauses(tcx: TyCtxt<'_>) -> GlobalOutlivesClauses<' &mut global_explicit_clauses, ); } + // 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() => { + insert_required_outlives_clauses_to_be_wf( + tcx, + tcx.type_of(item_did).instantiate_identity().skip_norm_wip(), + tcx.def_span(item_did), + &mut item_required_clauses, + &global_inferred_clauses, + &mut global_explicit_clauses, + ); + // FIXME(fmease): Walk the body, too, if it's direct. + } _ => {} }; diff --git a/compiler/rustc_hir_analysis/src/outlives/mod.rs b/compiler/rustc_hir_analysis/src/outlives/mod.rs index 33572ccb960be..5afb7f38de4d4 100644 --- a/compiler/rustc_hir_analysis/src/outlives/mod.rs +++ b/compiler/rustc_hir_analysis/src/outlives/mod.rs @@ -13,7 +13,7 @@ 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 => {} + DefKind::Struct | DefKind::Enum | DefKind::Union | DefKind::Const => {} DefKind::TyAlias if tcx.type_alias_is_checked(item_def_id) => {} DefKind::AnonConst if tcx.features().generic_const_exprs() diff --git a/src/doc/rustc-dev-guide/src/traits/implied-bounds.md b/src/doc/rustc-dev-guide/src/traits/implied-bounds.md index 1a2e9b225c21f..bbcdc882fa001 100644 --- a/src/doc/rustc-dev-guide/src/traits/implied-bounds.md +++ b/src/doc/rustc-dev-guide/src/traits/implied-bounds.md @@ -10,7 +10,8 @@ while implicit ones are handled, well... implicitly. ## explicit implied bounds The explicit implied bounds are computed in [`fn inferred_outlives_of`]. -Only ADTs and [CTAs](../appendix/glossary.md#cta) have explicit implied bounds +Only ADTs, [CTAs](../appendix/glossary.md#cta) +and free [GCIs] have explicit implied bounds which are computed via a fixpoint algorithm in the [`fn inferred_outlives_crate`] query. We use [`fn insert_required_clauses_to_be_wf`] on all fields of all ADTs in the crate. @@ -34,6 +35,7 @@ if the outlived region is a region parameter. [`fn check_explicit_clauses`]: https://github.com/rust-lang/rust/blob/5b8bc568d28b2e922290c9a966b3231d0ce9398b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs#L238 [`fn insert_outlives_clause`]: 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 + [GCIs]: https://github.com/rust-lang/rust/issues/113521 ## implicit implied bounds diff --git a/tests/ui/generic-const-items/assoc-const-missing-type.rs b/tests/ui/generic-const-items/assoc-const-missing-type.rs deleted file mode 100644 index 2b7167ad067e2..0000000000000 --- a/tests/ui/generic-const-items/assoc-const-missing-type.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Ensure that we properly deal with missing/placeholder types inside GACs. -// issue: rust-lang/rust#124833 -#![feature(generic_const_items)] -#![allow(incomplete_features)] - -trait Trait { - const K: T; - const Q<'a>: &'a str; -} - -impl Trait for () { - const K = (); - //~^ ERROR missing type for `const` item - //~| ERROR mismatched types - const Q = ""; - //~^ ERROR missing type for `const` item - //~| ERROR lifetime parameters or bounds on associated constant `Q` do not match the trait declaration -} - -fn main() {} diff --git a/tests/ui/generic-const-items/assoc-const-missing-type.stderr b/tests/ui/generic-const-items/assoc-const-missing-type.stderr deleted file mode 100644 index b7fb625a9ea53..0000000000000 --- a/tests/ui/generic-const-items/assoc-const-missing-type.stderr +++ /dev/null @@ -1,37 +0,0 @@ -error[E0308]: mismatched types - --> $DIR/assoc-const-missing-type.rs:12:18 - | -LL | const K = (); - | - - ^^ expected type parameter `T`, found `()` - | | | - | | expected because of the type of the associated constant - | expected this type parameter - | - = note: expected type parameter `T` - found unit type `()` - -error: missing type for `const` item - --> $DIR/assoc-const-missing-type.rs:12:15 - | -LL | const K = (); - | ^ help: provide a type for the associated constant: `()` - -error[E0195]: lifetime parameters or bounds on associated constant `Q` do not match the trait declaration - --> $DIR/assoc-const-missing-type.rs:15:12 - | -LL | const Q<'a>: &'a str; - | ---- lifetimes in impl do not match this associated constant in trait -... -LL | const Q = ""; - | ^ lifetimes do not match associated constant in trait - -error: missing type for `const` item - --> $DIR/assoc-const-missing-type.rs:15:12 - | -LL | const Q = ""; - | ^ help: provide a type for the associated constant: `: &str` - -error: aborting due to 4 previous errors - -Some errors have detailed explanations: E0195, E0308. -For more information about an error, try `rustc --explain E0195`. diff --git a/tests/ui/generic-const-items/implied-outlives-bounds-0.dump.stderr b/tests/ui/generic-const-items/implied-outlives-bounds-0.dump.stderr new file mode 100644 index 0000000000000..e6f4bcc18f66c --- /dev/null +++ b/tests/ui/generic-const-items/implied-outlives-bounds-0.dump.stderr @@ -0,0 +1,34 @@ +error: rustc_dump_inferred_outlives + --> $DIR/implied-outlives-bounds-0.rs:10:1 + | +LL | const REGION_OUTLIVES_0<'a, 'b>: Option<&'a &'b ()> = None; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 'b: 'a + +error: rustc_dump_inferred_outlives + --> $DIR/implied-outlives-bounds-0.rs:16:1 + | +LL | const REGION_OUTLIVES_1<'a, 'b>: &'a &'b () = &&(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 'b: 'a + +error: rustc_dump_inferred_outlives + --> $DIR/implied-outlives-bounds-0.rs:22:1 + | +LL | const TYPE_OUTLIVES_0<'a, T>: Option<&'a T> = None; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: T: 'a + +error: rustc_dump_inferred_outlives + --> $DIR/implied-outlives-bounds-0.rs:28:1 + | +LL | const TYPE_OUTLIVES_1<'a, T: Def>: &'a T = &T::DEF; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: T: 'a + +error: aborting due to 4 previous errors + diff --git a/tests/ui/generic-const-items/implied-outlives-bounds-0.fail.stderr b/tests/ui/generic-const-items/implied-outlives-bounds-0.fail.stderr new file mode 100644 index 0000000000000..bbb73f6d2c0f6 --- /dev/null +++ b/tests/ui/generic-const-items/implied-outlives-bounds-0.fail.stderr @@ -0,0 +1,18 @@ +error: lifetime may not live long enough + --> $DIR/implied-outlives-bounds-0.rs:36:40 + | +LL | #[cfg(fail)] fn env0<'any>() { let _ = REGION_OUTLIVES_0::<'static, 'any>; } + | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'any` must outlive `'static` + | | + | lifetime `'any` defined here + +error: lifetime may not live long enough + --> $DIR/implied-outlives-bounds-0.rs:39:40 + | +LL | #[cfg(fail)] fn env1<'any>() { let _ = TYPE_OUTLIVES_0::<'static, &'any ()>; } + | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'any` must outlive `'static` + | | + | lifetime `'any` defined here + +error: aborting due to 2 previous errors + diff --git a/tests/ui/generic-const-items/implied-outlives-bounds-0.rs b/tests/ui/generic-const-items/implied-outlives-bounds-0.rs new file mode 100644 index 0000000000000..294e2b5359406 --- /dev/null +++ b/tests/ui/generic-const-items/implied-outlives-bounds-0.rs @@ -0,0 +1,42 @@ +// Check that we imply outlives-bounds on (generic) free const items. +//@ revisions: pass fail dump +//@[pass] check-pass +#![feature(generic_const_items)] +#![feature(freeze)] // only used in the test case `TYPE_OUTLIVES_1` +#![cfg_attr(dump, feature(rustc_attrs))] + +// We imply `'b: 'a` from type `&'a &'b ()` in the constant's type. +#[cfg_attr(dump, rustc_dump_inferred_outlives)] +const REGION_OUTLIVES_0<'a, 'b>: Option<&'a &'b ()> = None; +//[dump]~^ ERROR rustc_dump_inferred_outlives +//[dump]~| NOTE: 'b: 'a + +// We imply `'b: 'a` from type `&'a &'b ()` in the constant's type. +#[cfg_attr(dump, rustc_dump_inferred_outlives)] +const REGION_OUTLIVES_1<'a, 'b>: &'a &'b () = &&(); +//[dump]~^ ERROR rustc_dump_inferred_outlives +//[dump]~| NOTE: 'b: 'a + +// We imply `T: 'a` from type `&'a T` in the constant's type. +#[cfg_attr(dump, rustc_dump_inferred_outlives)] +const TYPE_OUTLIVES_0<'a, T>: Option<&'a T> = None; +//[dump]~^ ERROR rustc_dump_inferred_outlives +//[dump]~| NOTE: T: 'a + +// We imply `T: 'a` from type `&'a T` in the constant's type. +#[cfg_attr(dump, rustc_dump_inferred_outlives)] +const TYPE_OUTLIVES_1<'a, T: Def>: &'a T = &T::DEF; +//[dump]~^ ERROR rustc_dump_inferred_outlives +//[dump]~| NOTE: T: 'a + +trait Def: std::marker::Freeze { const DEF: Self; } + +// Ensure that we actually enforce these implied bounds at usage sites: + +#[cfg(fail)] fn env0<'any>() { let _ = REGION_OUTLIVES_0::<'static, 'any>; } +//[fail]~^ ERROR lifetime may not live long enough + +#[cfg(fail)] fn env1<'any>() { let _ = TYPE_OUTLIVES_0::<'static, &'any ()>; } +//[fail]~^ ERROR lifetime may not live long enough + +fn main() {} diff --git a/tests/ui/generic-const-items/missing-or-inferred-type.rs b/tests/ui/generic-const-items/missing-or-inferred-type.rs new file mode 100644 index 0000000000000..09d98324a2cf1 --- /dev/null +++ b/tests/ui/generic-const-items/missing-or-inferred-type.rs @@ -0,0 +1,39 @@ +// Ensure that we properly deal with missing signatures and inferred types `_` in the signature. +#![feature(generic_const_items)] +#![expect(incomplete_features)] + +// For implicit & explicit inferred types in the item signature we generally try to infer the +// type of the body which we can then suggest to the user. + +// However, in the type of trait impl assoc items specifically we first try to suggest the type of +// the corresponding definition in the trait. While doing so, we once didn't use to instantiate GACs +// correctly & couldn't handle mismatches in parameter list lengths very well (impl v trait). +// issue: + +trait Trait { + const K: T; + const Q<'a>: &'a str; +} + +impl Trait for () { + const K = (); + //~^ ERROR missing type for `const` item + //~| ERROR mismatched types + //~| SUGGESTION () + const Q = ""; + //~^ ERROR missing type for `const` item + //~| ERROR lifetime parameters or bounds on associated constant `Q` do not match the trait declaration + //~| SUGGESTION : &str +} + +// For parametrized free const items however, we can't typeck the body without causing a query cycle +// so we don't and thus fall back to a generic suggestion that has a placeholder. + +const _ = loop {}; +//~^ ERROR missing type for `const` item +//~| SUGGESTION + +const _: _ = 0; +//~^ ERROR the placeholder `_` is not allowed + +fn main() {} diff --git a/tests/ui/generic-const-items/missing-or-inferred-type.stderr b/tests/ui/generic-const-items/missing-or-inferred-type.stderr new file mode 100644 index 0000000000000..245808448b142 --- /dev/null +++ b/tests/ui/generic-const-items/missing-or-inferred-type.stderr @@ -0,0 +1,54 @@ +error[E0308]: mismatched types + --> $DIR/missing-or-inferred-type.rs:19:18 + | +LL | const K = (); + | - - ^^ expected type parameter `T`, found `()` + | | | + | | expected because of the type of the associated constant + | expected this type parameter + | + = note: expected type parameter `T` + found unit type `()` + +error: missing type for `const` item + --> $DIR/missing-or-inferred-type.rs:19:15 + | +LL | const K = (); + | ^ help: provide a type for the associated constant: `()` + +error[E0195]: lifetime parameters or bounds on associated constant `Q` do not match the trait declaration + --> $DIR/missing-or-inferred-type.rs:23:13 + | +LL | const Q<'a>: &'a str; + | ---- lifetimes in impl do not match this associated constant in trait +... +LL | const Q = ""; + | ^ lifetimes do not match associated constant in trait + +error[E0121]: the placeholder `_` is not allowed within types on item signatures for constants + --> $DIR/missing-or-inferred-type.rs:36:13 + | +LL | const _: _ = 0; + | ^ not allowed in type signatures + +error: missing type for `const` item + --> $DIR/missing-or-inferred-type.rs:23:13 + | +LL | const Q = ""; + | ^ help: provide a type for the associated constant: `: &str` + +error: missing type for `const` item + --> $DIR/missing-or-inferred-type.rs:32:11 + | +LL | const _ = loop {}; + | ^ + | +help: provide a type for the item + | +LL | const _: = loop {}; + | ++++++++ + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0121, E0195, E0308. +For more information about an error, try `rustc --explain E0121`. diff --git a/tests/ui/generic-const-items/reference-outlives-referent.rs b/tests/ui/generic-const-items/reference-outlives-referent.rs index 13e4eaac39f88..ba9bae688584c 100644 --- a/tests/ui/generic-const-items/reference-outlives-referent.rs +++ b/tests/ui/generic-const-items/reference-outlives-referent.rs @@ -2,8 +2,17 @@ // 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 C<'b, 'c>: &'c &'b () = &&(); //~ ERROR reference has a longer lifetime than the data it references + 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() {} diff --git a/tests/ui/generic-const-items/reference-outlives-referent.stderr b/tests/ui/generic-const-items/reference-outlives-referent.stderr index 34553c51654bb..427bd4a832564 100644 --- a/tests/ui/generic-const-items/reference-outlives-referent.stderr +++ b/tests/ui/generic-const-items/reference-outlives-referent.stderr @@ -1,20 +1,49 @@ +error[E0491]: in type `&'c &'b ()`, reference has a longer lifetime than the data it references + --> $DIR/reference-outlives-referent.rs:10:5 + | +LL | const C<'b, 'c>: &'c &'b () = &&(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the pointer is valid for the lifetime `'c` as defined here + --> $DIR/reference-outlives-referent.rs:10:17 + | +LL | const C<'b, 'c>: &'c &'b () = &&(); + | ^^ +note: but the referenced data is only valid for the lifetime `'b` as defined here + --> $DIR/reference-outlives-referent.rs:10:13 + | +LL | const C<'b, 'c>: &'c &'b () = &&(); + | ^^ + 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:11: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:11: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:15: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 3 previous errors For more information about this error, try `rustc --explain E0491`. From e5380e47b1af5c7a861cc0a621e180577486ff2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 9 Oct 2026 01:11:50 +0200 Subject: [PATCH 4/6] GCI: Imply outlives-bounds on free (generic) const items from their RHS if direct --- compiler/rustc_hir_analysis/src/collect.rs | 4 +- .../src/outlives/implicit_infer.rs | 261 +++++++++++------- .../implied-outlives-bounds-1.dump.stderr | 34 +++ .../implied-outlives-bounds-1.fail.stderr | 34 +++ .../implied-outlives-bounds-1.rs | 59 ++++ 5 files changed, 290 insertions(+), 102 deletions(-) create mode 100644 tests/ui/generic-const-items/implied-outlives-bounds-1.dump.stderr create mode 100644 tests/ui/generic-const-items/implied-outlives-bounds-1.fail.stderr create mode 100644 tests/ui/generic-const-items/implied-outlives-bounds-1.rs diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index ecfa8f6c67be6..d52e487b64914 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1834,9 +1834,7 @@ fn const_of_item<'tcx>( }; let ct_arg = match ct_rhs { hir::ConstItemRhs::Direct(ct_arg) => ct_arg, - hir::ConstItemRhs::Body(_) => { - return None; - } + hir::ConstItemRhs::Body(_) => return None, }; let icx = ItemCtxt::new(tcx, def_id); let identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); diff --git a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs index 6cd66efe077dc..093d2259dcdd5 100644 --- a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs +++ b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs @@ -30,9 +30,10 @@ pub(super) fn infer_outlives_clauses(tcx: TyCtxt<'_>) -> GlobalOutlivesClauses<' let adt_def = tcx.adt_def(item_did.to_def_id()); for field_def in adt_def.all_fields() { + let ty = tcx.type_of(field_def.did).instantiate_identity().skip_norm_wip(); insert_required_outlives_clauses_to_be_wf( tcx, - tcx.type_of(field_def.did).instantiate_identity().skip_norm_wip(), + ty.into(), tcx.def_span(field_def.did), &mut item_required_clauses, &global_inferred_clauses, @@ -41,9 +42,10 @@ pub(super) fn infer_outlives_clauses(tcx: TyCtxt<'_>) -> GlobalOutlivesClauses<' } } DefKind::TyAlias if tcx.type_alias_is_checked(item_did) => { + let ty = tcx.type_of(item_did).instantiate_identity().skip_norm_wip(); insert_required_outlives_clauses_to_be_wf( tcx, - tcx.type_of(item_did).instantiate_identity().skip_norm_wip(), + ty.into(), tcx.def_span(item_did), &mut item_required_clauses, &global_inferred_clauses, @@ -58,15 +60,27 @@ pub(super) fn infer_outlives_clauses(tcx: TyCtxt<'_>) -> GlobalOutlivesClauses<' // 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() => { + let span = tcx.def_span(item_did); + insert_required_outlives_clauses_to_be_wf( tcx, - tcx.type_of(item_did).instantiate_identity().skip_norm_wip(), - tcx.def_span(item_did), + tcx.type_of(item_did).instantiate_identity().skip_norm_wip().into(), + span, &mut item_required_clauses, &global_inferred_clauses, &mut global_explicit_clauses, ); - // FIXME(fmease): Walk the body, too, if it's direct. + + if let Some(ct) = tcx.const_of_item(item_did) { + insert_required_outlives_clauses_to_be_wf( + tcx, + ct.instantiate_identity().skip_norm_wip().into(), + span, + &mut item_required_clauses, + &global_inferred_clauses, + &mut global_explicit_clauses, + ); + } } _ => {} }; @@ -109,111 +123,160 @@ pub(super) fn infer_outlives_clauses(tcx: TyCtxt<'_>) -> GlobalOutlivesClauses<' fn insert_required_outlives_clauses_to_be_wf<'tcx>( tcx: TyCtxt<'tcx>, - ty: Ty<'tcx>, + arg: ty::GenericArg<'tcx>, span: Span, required_clauses: &mut OutlivesClauses<'tcx>, global_inferred_clauses: &GlobalOutlivesClauses<'tcx>, global_explicit_clauses: &mut GlobalExplicitOutlivesClauses<'tcx>, ) { - for arg in ty.walk() { - let leaf_ty = match arg.kind() { - GenericArgKind::Type(ty) => ty, - // No clauses from lifetimes or constants, except potentially - // constants' types, but `walk` will get to them as well. - GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue, - }; - - match *leaf_ty.kind() { - ty::Ref(region, rty, _) => { - // The type `&'a T` inherently has the requirement `T: 'a`. - insert_outlives_clause(tcx, rty.into(), region, span, required_clauses); - } - - ty::Adt(def, args) => { - check_inferred_clauses( - tcx, - def.did(), - args, - global_inferred_clauses, - required_clauses, - ); - check_explicit_clauses( - tcx, - def.did(), - args, - required_clauses, - global_explicit_clauses, - IgnoreClausesReferencingSelf::No, - ); - } - - ty::Alias(_, ty::AliasTy { kind: ty::Free { def_id }, args, .. }) => { - check_inferred_clauses( - tcx, - def_id, - args, - global_inferred_clauses, - required_clauses, - ); - check_explicit_clauses( - tcx, - def_id, - args, - required_clauses, - global_explicit_clauses, - IgnoreClausesReferencingSelf::No, - ); - } - - ty::Dynamic(obj, ..) if let Some(principal_trait_ref) = obj.principal() => { - let args = principal_trait_ref - .with_self_ty(tcx, tcx.types.trait_object_dummy_self) - .skip_binder() - .args; - // We skip clauses that reference the `Self` type parameter since we don't - // want to leak the dummy Self to the clauses map. - // - // While filtering out bounds like `Self: 'a` as in `trait Trait<'a, T>: 'a {}` - // doesn't matter since they can't affect the lifetime / type parameters anyway, - // for bounds like `Self::AssocTy: 'b` which we of course currently also ignore - // (see also #54467) it might conceivably be better to extract the binding - // `AssocTy = U` from the trait object type (which must exist) and thus infer - // an outlives requirement that `U: 'b`. - check_explicit_clauses( - tcx, - principal_trait_ref.def_id(), - args, - required_clauses, - global_explicit_clauses, - IgnoreClausesReferencingSelf::Yes, - ); - } + for arg in arg.walk() { + match arg.kind() { + GenericArgKind::Lifetime(_) => {} + GenericArgKind::Type(ty) => check_leaf_ty( + tcx, + ty, + span, + required_clauses, + global_inferred_clauses, + global_explicit_clauses, + ), + GenericArgKind::Const(ct) => check_leaf_ct( + tcx, + ct, + required_clauses, + global_inferred_clauses, + global_explicit_clauses, + ), + } + } +} - ty::Alias(_, ty::AliasTy { kind: ty::Projection { def_id }, args, .. }) => { - // We only use the explicit clauses of the trait but not the ones of the associated - // type itself. FIXME(#141692): Ideally we would consider them, too, though. - // - // E.g., for `<() as Trait<'b, U>>::Type` we will infer `U: 'b` (modulo component - // splitting) given `trait Trait<'a, T: 'a> { type Type; }` if `'b` is early-bound. - check_explicit_clauses( - tcx, - tcx.parent(def_id), - args, - required_clauses, - global_explicit_clauses, - IgnoreClausesReferencingSelf::No, - ); - } +fn check_leaf_ty<'tcx>( + tcx: TyCtxt<'tcx>, + leaf_ty: Ty<'tcx>, + span: Span, + required_clauses: &mut OutlivesClauses<'tcx>, + global_inferred_clauses: &GlobalOutlivesClauses<'tcx>, + global_explicit_clauses: &mut GlobalExplicitOutlivesClauses<'tcx>, +) { + match *leaf_ty.kind() { + ty::Ref(region, rty, _) => { + // The type `&'a T` inherently has the requirement `T: 'a`. + insert_outlives_clause(tcx, rty.into(), region, span, required_clauses); + } + ty::Adt(def, args) => { + check_inferred_clauses(tcx, def.did(), args, global_inferred_clauses, required_clauses); + check_explicit_clauses( + tcx, + def.did(), + args, + required_clauses, + global_explicit_clauses, + IgnoreClausesReferencingSelf::No, + ); + } + ty::Dynamic(obj, ..) if let Some(principal_trait_ref) = obj.principal() => { + let args = principal_trait_ref + .with_self_ty(tcx, tcx.types.trait_object_dummy_self) + .skip_binder() + .args; + // We skip clauses that reference the `Self` type parameter since we don't + // want to leak the dummy Self to the clauses map. + // + // While filtering out bounds like `Self: 'a` as in `trait Trait<'a, T>: 'a {}` + // doesn't matter since they can't affect the lifetime / type parameters anyway, + // for bounds like `Self::AssocTy: 'b` which we of course currently also ignore + // (see also #54467) it might conceivably be better to extract the binding + // `AssocTy = U` from the trait object type (which must exist) and thus infer + // an outlives requirement that `U: 'b`. + check_explicit_clauses( + tcx, + principal_trait_ref.def_id(), + args, + required_clauses, + global_explicit_clauses, + IgnoreClausesReferencingSelf::Yes, + ); + } + ty::Alias(_, ty::AliasTy { kind: ty::Free { def_id }, args, .. }) => { + check_inferred_clauses(tcx, def_id, args, global_inferred_clauses, required_clauses); + check_explicit_clauses( + tcx, + def_id, + args, + required_clauses, + global_explicit_clauses, + IgnoreClausesReferencingSelf::No, + ); + } + ty::Alias(_, ty::AliasTy { kind: ty::Projection { def_id }, args, .. }) => { + // We only use the explicit clauses of the trait but not the ones of the associated + // type itself. FIXME(#141692): Ideally we would consider them, too, though. + // + // E.g., for `<() as Trait<'b, U>>::Type` we will infer `U: 'b` (modulo component + // splitting) given `trait Trait<'a, T: 'a> { type Type; }` if `'b` is early-bound. + check_explicit_clauses( + tcx, + tcx.parent(def_id), + args, + required_clauses, + global_explicit_clauses, + IgnoreClausesReferencingSelf::No, + ); + } + // FIXME(inherent_associated_types): Use the explicit clauses from the parent impl. + ty::Alias(_, ty::AliasTy { kind: ty::Inherent { .. }, .. }) => {} + _ => {} + } +} - // FIXME(inherent_associated_types): Use the explicit clauses from the parent impl. - ty::Alias(_, ty::AliasTy { kind: ty::Inherent { .. }, .. }) => {} +fn check_leaf_ct<'tcx>( + tcx: TyCtxt<'tcx>, + leaf_ct: ty::Const<'tcx>, + required_clauses: &mut OutlivesClauses<'tcx>, + global_inferred_clauses: &GlobalOutlivesClauses<'tcx>, + global_explicit_clauses: &mut GlobalExplicitOutlivesClauses<'tcx>, +) { + // FIXME(mgca): Eventually infer bounds from struct literals if feasible? So `ConstKind::Value` + // (which we know is unnormalized at this point) where the corresponding `Ty` is an + // ADT to mirror the `Adt` case in `check_leaf_ty` above? - _ => {} + match leaf_ct.kind() { + ty::ConstKind::Alias( + _, + ty::AliasConst { kind: ty::AliasConstKind::Free { def_id }, args, .. }, + ) => { + check_inferred_clauses(tcx, def_id, args, global_inferred_clauses, required_clauses); + check_explicit_clauses( + tcx, + def_id, + args, + required_clauses, + global_explicit_clauses, + IgnoreClausesReferencingSelf::No, + ); + } + ty::ConstKind::Alias( + _, + ty::AliasConst { kind: ty::AliasConstKind::Projection { def_id }, args, .. }, + ) => { + // This corresponds to a constant like `<() as Trait<'a, T>>::CONST`. + // We only use the explicit clauses of the trait but not the ones of the associated item + // itself. FIXME(#141692): Ideally we would consider them, too, though. + check_explicit_clauses( + tcx, + tcx.parent(def_id), + args, + required_clauses, + global_explicit_clauses, + IgnoreClausesReferencingSelf::No, + ); } + _ => {} } } -/// Check the explicit clauses declared on the type. +/// Check the explicit clauses declared on the item. /// /// ### Example /// @@ -263,7 +326,7 @@ enum IgnoreClausesReferencingSelf { No, } -/// Check the inferred clauses of the type. +/// Check the inferred clauses of the item. /// /// ### Example /// diff --git a/tests/ui/generic-const-items/implied-outlives-bounds-1.dump.stderr b/tests/ui/generic-const-items/implied-outlives-bounds-1.dump.stderr new file mode 100644 index 0000000000000..6ef88c4e50da1 --- /dev/null +++ b/tests/ui/generic-const-items/implied-outlives-bounds-1.dump.stderr @@ -0,0 +1,34 @@ +error: rustc_dump_inferred_outlives + --> $DIR/implied-outlives-bounds-1.rs:13:1 + | +LL | const CT0<'a, 'b>: usize = std::gca!(EXP::<'a, 'b>); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 'b: 'a + +error: rustc_dump_inferred_outlives + --> $DIR/implied-outlives-bounds-1.rs:19:1 + | +LL | struct Ty0<'a, 'b>([(); std::gca!(EXP::<'a, 'b>)]); + | ^^^^^^^^^^^^^^^^^^ + | + = note: 'b: 'a + +error: rustc_dump_inferred_outlives + --> $DIR/implied-outlives-bounds-1.rs:29:1 + | +LL | const CT1<'a, 'b>: usize = std::gca!(IMP::<'a, 'b>); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: 'b: 'a + +error: rustc_dump_inferred_outlives + --> $DIR/implied-outlives-bounds-1.rs:35:1 + | +LL | struct Ty1<'a, 'b>([(); std::gca!(IMP::<'a, 'b>)]); + | ^^^^^^^^^^^^^^^^^^ + | + = note: 'b: 'a + +error: aborting due to 4 previous errors + diff --git a/tests/ui/generic-const-items/implied-outlives-bounds-1.fail.stderr b/tests/ui/generic-const-items/implied-outlives-bounds-1.fail.stderr new file mode 100644 index 0000000000000..d26e7e447b359 --- /dev/null +++ b/tests/ui/generic-const-items/implied-outlives-bounds-1.fail.stderr @@ -0,0 +1,34 @@ +error: lifetime may not live long enough + --> $DIR/implied-outlives-bounds-1.rs:46:40 + | +LL | #[cfg(fail)] fn env0<'any>() { let _ = CT0::<'static, 'any>; } + | ---- ^^^^^^^^^^^^^^^^^^^^ requires that `'any` must outlive `'static` + | | + | lifetime `'any` defined here + +error: lifetime may not live long enough + --> $DIR/implied-outlives-bounds-1.rs:49:39 + | +LL | #[cfg(fail)] fn env1<'any>() { let _: Ty0<'static, 'any>; } + | ---- ^^^^^^^^^^^^^^^^^^ requires that `'any` must outlive `'static` + | | + | lifetime `'any` defined here + +error: lifetime may not live long enough + --> $DIR/implied-outlives-bounds-1.rs:52:40 + | +LL | #[cfg(fail)] fn env2<'any>() { let _ = CT1::<'static, 'any>; } + | ---- ^^^^^^^^^^^^^^^^^^^^ requires that `'any` must outlive `'static` + | | + | lifetime `'any` defined here + +error: lifetime may not live long enough + --> $DIR/implied-outlives-bounds-1.rs:55:40 + | +LL | #[cfg(fail)] fn env3<'any>() { let _ = Ty1::<'static, 'any>; } + | ---- ^^^^^^^^^^^^^^^^^^^^ requires that `'any` must outlive `'static` + | | + | lifetime `'any` defined here + +error: aborting due to 4 previous errors + diff --git a/tests/ui/generic-const-items/implied-outlives-bounds-1.rs b/tests/ui/generic-const-items/implied-outlives-bounds-1.rs new file mode 100644 index 0000000000000..e76aef20f1b4d --- /dev/null +++ b/tests/ui/generic-const-items/implied-outlives-bounds-1.rs @@ -0,0 +1,59 @@ +// Check that paths referring to (generic) free const items induce implied bounds. +//@ revisions: pass fail dump +//@[pass] check-pass +#![feature(generic_const_items, gca_min_const_items)] +#![cfg_attr(dump, feature(rustc_attrs))] + +// References of `EXP::<'r, 'q>` (`'r`, `'q` early-bound) should induce implied bound `'q: 'r` in +// the enclosing definition due to the explicit outlives-bound `'b: 'a` on `EXP`. +const EXP<'a, 'b: 'a>: usize = std::gca!(0); + +// We imply `'b: 'a` from const alias `EXP::<'a, 'b>` in the constant's body. +#[cfg_attr(dump, rustc_dump_inferred_outlives)] +const CT0<'a, 'b>: usize = std::gca!(EXP::<'a, 'b>); +//[dump]~^ ERROR rustc_dump_inferred_outlives +//[dump]~| NOTE: 'b: 'a + +// We imply `'b: 'a` from const alias `EXP::<'a, 'b>` in the struct's signature. +#[cfg_attr(dump, rustc_dump_inferred_outlives)] +struct Ty0<'a, 'b>([(); std::gca!(EXP::<'a, 'b>)]); +//[dump]~^ ERROR rustc_dump_inferred_outlives +//[dump]~| NOTE: 'b: 'a + +// References of `IMP::<'r, 'q>` (`'r`, `'q` early-bound) should induce implied bound `'q: 'r` in +// the enclosing definition due to the implicit outlives-bound `'b: 'a` on `IMP`. +const IMP<'a, 'b>: usize = std::gca!(EXP::<'a, 'b>); + +// We imply `'b: 'a` from const alias `EXP::<'a, 'b>` in the constant's body. +#[cfg_attr(dump, rustc_dump_inferred_outlives)] +const CT1<'a, 'b>: usize = std::gca!(IMP::<'a, 'b>); +//[dump]~^ ERROR rustc_dump_inferred_outlives +//[dump]~| NOTE: 'b: 'a + +// We imply `'b: 'a` from const alias `EXP::<'a, 'b>` in the struct's signature. +#[cfg_attr(dump, rustc_dump_inferred_outlives)] +struct Ty1<'a, 'b>([(); std::gca!(IMP::<'a, 'b>)]); +//[dump]~^ ERROR rustc_dump_inferred_outlives +//[dump]~| NOTE: 'b: 'a + +const CT2<'a, 'b>: [(); std::gca!(EXP::<'a, 'b>)] = { + let _: &'a &'b (); // OK since `EXP::<'a, 'b>` implies `'b: 'a` + [] +}; + +// Ensure that we actually enforce these implied bounds at usage sites: + +#[cfg(fail)] fn env0<'any>() { let _ = CT0::<'static, 'any>; } +//[fail]~^ ERROR lifetime may not live long enough + +#[cfg(fail)] fn env1<'any>() { let _: Ty0<'static, 'any>; } +//[fail]~^ ERROR lifetime may not live long enough + +#[cfg(fail)] fn env2<'any>() { let _ = CT1::<'static, 'any>; } +//[fail]~^ ERROR lifetime may not live long enough + +#[cfg(fail)] fn env3<'any>() { let _ = Ty1::<'static, 'any>; } +//[fail]~^ ERROR lifetime may not live long enough + + +fn main() {} From 557438a22448367e0da3c06812b11c2b1a8d0478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 18 Oct 2026 12:04:28 +0200 Subject: [PATCH 5/6] --------------- BRANCH SEPARATOR --------------- From 0c1c7afc41981b43df40ce487cec668f6f31e138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 23 Oct 2026 15:42:00 +0200 Subject: [PATCH 6/6] Extend support of lint `explict_outlives_requirements` to (generic) const items --- compiler/rustc_lint/src/builtin.rs | 328 +++++++++--------- .../lint-explicit-outlives-requirements.fixed | 14 + .../lint-explicit-outlives-requirements.rs | 14 + ...lint-explicit-outlives-requirements.stderr | 20 ++ .../edition-lint-infer-outlives-macro.fixed | 8 +- .../edition-lint-infer-outlives-macro.stderr | 16 +- ...ition-lint-infer-outlives-multispan.stderr | 8 +- .../edition-lint-infer-outlives.fixed | 16 +- .../edition-lint-infer-outlives.stderr | 32 +- 9 files changed, 250 insertions(+), 206 deletions(-) create mode 100644 tests/ui/generic-const-items/lint-explicit-outlives-requirements.fixed create mode 100644 tests/ui/generic-const-items/lint-explicit-outlives-requirements.rs create mode 100644 tests/ui/generic-const-items/lint-explicit-outlives-requirements.stderr diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 2b1e0a24294d2..356239410bb83 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1976,204 +1976,200 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { use rustc_middle::middle::resolve_bound_vars::ResolvedArg; let def_id = item.owner_id.def_id; - if let hir::ItemKind::Struct(_, generics, _) + + let (hir::ItemKind::Struct(_, generics, _) | hir::ItemKind::Enum(_, generics, _) - | hir::ItemKind::Union(_, generics, _) = item.kind - { - let inferred_outlives = cx.tcx.inferred_outlives_of(def_id); - if inferred_outlives.is_empty() { - return; - } + | hir::ItemKind::Union(_, generics, _) + | hir::ItemKind::Const(_, generics, _, _)) = item.kind + else { + return; + }; + + let inferred_outlives = cx.tcx.inferred_outlives_of(def_id); + if inferred_outlives.is_empty() { + return; + } - let ty_generics = cx.tcx.generics_of(def_id); - let num_where_predicates = generics - .predicates - .iter() - .filter(|predicate| predicate.kind.in_where_clause()) - .count(); - - let mut bound_count = 0; - let mut lint_spans = Vec::new(); - let mut where_lint_spans = Vec::new(); - let mut dropped_where_predicate_count = 0; - for (i, where_predicate) in generics.predicates.iter().enumerate() { - let (relevant_lifetimes, bounds, predicate_span, in_where_clause) = - match where_predicate.kind { - hir::WherePredicateKind::RegionPredicate(predicate) => { - if let Some(ResolvedArg::EarlyBound(region_def_id)) = - cx.tcx.named_bound_var(predicate.lifetime.hir_id) - { + let ty_generics = cx.tcx.generics_of(def_id); + let num_where_predicates = + generics.predicates.iter().filter(|predicate| predicate.kind.in_where_clause()).count(); + + let mut bound_count = 0; + let mut lint_spans = Vec::new(); + let mut where_lint_spans = Vec::new(); + let mut dropped_where_predicate_count = 0; + for (i, where_predicate) in generics.predicates.iter().enumerate() { + let (relevant_lifetimes, bounds, predicate_span, in_where_clause) = + match where_predicate.kind { + hir::WherePredicateKind::RegionPredicate(predicate) => { + if let Some(ResolvedArg::EarlyBound(region_def_id)) = + cx.tcx.named_bound_var(predicate.lifetime.hir_id) + { + ( + Self::lifetimes_outliving_lifetime( + cx.tcx, + // don't warn if the inferred span actually came from the predicate we're looking at + // this happens if the type is recursively defined + inferred_outlives + .iter() + .filter(|(_, span)| !where_predicate.span.contains(*span)), + item.owner_id.def_id, + region_def_id, + ), + &predicate.bounds, + where_predicate.span, + predicate.in_where_clause, + ) + } else { + continue; + } + } + hir::WherePredicateKind::BoundPredicate(predicate) => { + // FIXME we can also infer bounds on associated types, + // and should check for them here. + match predicate.bounded_ty.kind { + hir::TyKind::Path(hir::QPath::Resolved(None, path)) => { + let Res::Def(DefKind::TyParam, def_id) = path.res else { + continue; + }; + let index = ty_generics.param_def_id_to_index[&def_id]; + // Removing a `T: 'r` outlives bound can silently change + // the object lifetime default for `Struct<'r, dyn Trait>` + // (RFC 599): the explicit bound sets the default to `'r`, + // so removing it may change it to `'static` (or cause an + // ambiguity error if there is no unique default). Only + // suppress the lint for non-higher-ranked predicates when + // T is not `Sized` (i.e. can hold trait object types). + // Higher-ranked predicates (`for<'x> T: 'r`) are excluded + // from RFC 599 object lifetime defaulting and are always + // safe to remove. + if predicate.bound_generic_params.is_empty() { + let ty_param = &ty_generics.own_params[index as usize]; + let param_ty = + Ty::new_param(cx.tcx, ty_param.index, ty_param.name); + if !param_ty.is_sized(cx.tcx, cx.typing_env()) { + continue; + } + } ( - Self::lifetimes_outliving_lifetime( - cx.tcx, + Self::lifetimes_outliving_type( // don't warn if the inferred span actually came from the predicate we're looking at // this happens if the type is recursively defined inferred_outlives.iter().filter(|(_, span)| { !where_predicate.span.contains(*span) }), - item.owner_id.def_id, - region_def_id, + index, ), &predicate.bounds, where_predicate.span, - predicate.in_where_clause, + predicate.origin == PredicateOrigin::WhereClause, ) - } else { - continue; } - } - hir::WherePredicateKind::BoundPredicate(predicate) => { - // FIXME we can also infer bounds on associated types, - // and should check for them here. - match predicate.bounded_ty.kind { - hir::TyKind::Path(hir::QPath::Resolved(None, path)) => { - let Res::Def(DefKind::TyParam, def_id) = path.res else { - continue; - }; - let index = ty_generics.param_def_id_to_index[&def_id]; - // Removing a `T: 'r` outlives bound can silently change - // the object lifetime default for `Struct<'r, dyn Trait>` - // (RFC 599): the explicit bound sets the default to `'r`, - // so removing it may change it to `'static` (or cause an - // ambiguity error if there is no unique default). Only - // suppress the lint for non-higher-ranked predicates when - // T is not `Sized` (i.e. can hold trait object types). - // Higher-ranked predicates (`for<'x> T: 'r`) are excluded - // from RFC 599 object lifetime defaulting and are always - // safe to remove. - if predicate.bound_generic_params.is_empty() { - let ty_param = &ty_generics.own_params[index as usize]; - let param_ty = - Ty::new_param(cx.tcx, ty_param.index, ty_param.name); - if !param_ty.is_sized(cx.tcx, cx.typing_env()) { - continue; - } - } - ( - Self::lifetimes_outliving_type( - // don't warn if the inferred span actually came from the predicate we're looking at - // this happens if the type is recursively defined - inferred_outlives.iter().filter(|(_, span)| { - !where_predicate.span.contains(*span) - }), - index, - ), - &predicate.bounds, - where_predicate.span, - predicate.origin == PredicateOrigin::WhereClause, - ) - } - _ => { - continue; - } + _ => { + continue; } } - }; - if relevant_lifetimes.is_empty() { - continue; - } + } + }; + if relevant_lifetimes.is_empty() { + continue; + } - let bound_spans = self.collect_outlives_bound_spans( - cx.tcx, - bounds, - &relevant_lifetimes, - predicate_span, - item.owner_id.to_def_id(), - ); - bound_count += bound_spans.len(); + let bound_spans = self.collect_outlives_bound_spans( + cx.tcx, + bounds, + &relevant_lifetimes, + predicate_span, + item.owner_id.to_def_id(), + ); + bound_count += bound_spans.len(); - let drop_predicate = bound_spans.len() == bounds.len(); - if drop_predicate && in_where_clause { - dropped_where_predicate_count += 1; - } + let drop_predicate = bound_spans.len() == bounds.len(); + if drop_predicate && in_where_clause { + dropped_where_predicate_count += 1; + } - if drop_predicate { - if !in_where_clause { - lint_spans.push(predicate_span); - } else if predicate_span.from_expansion() { - // Don't try to extend the span if it comes from a macro expansion. + if drop_predicate { + if !in_where_clause { + lint_spans.push(predicate_span); + } else if predicate_span.from_expansion() { + // Don't try to extend the span if it comes from a macro expansion. + where_lint_spans.push(predicate_span); + } else if i + 1 < num_where_predicates { + // If all the bounds on a predicate were inferable and there are + // further predicates, we want to eat the trailing comma. + let next_predicate_span = generics.predicates[i + 1].span; + if next_predicate_span.from_expansion() { where_lint_spans.push(predicate_span); - } else if i + 1 < num_where_predicates { - // If all the bounds on a predicate were inferable and there are - // further predicates, we want to eat the trailing comma. - let next_predicate_span = generics.predicates[i + 1].span; - if next_predicate_span.from_expansion() { - where_lint_spans.push(predicate_span); - } else { - where_lint_spans - .push(predicate_span.to(next_predicate_span.shrink_to_lo())); - } } else { - // Eat the optional trailing comma after the last predicate. - let where_span = generics.where_clause_span; - if where_span.from_expansion() { - where_lint_spans.push(predicate_span); - } else { - where_lint_spans.push(predicate_span.to(where_span.shrink_to_hi())); - } + where_lint_spans + .push(predicate_span.to(next_predicate_span.shrink_to_lo())); } } else { - where_lint_spans.extend(self.consolidate_outlives_bound_spans( - predicate_span.shrink_to_lo(), - bounds, - bound_spans, - )); + // Eat the optional trailing comma after the last predicate. + let where_span = generics.where_clause_span; + if where_span.from_expansion() { + where_lint_spans.push(predicate_span); + } else { + where_lint_spans.push(predicate_span.to(where_span.shrink_to_hi())); + } } + } else { + where_lint_spans.extend(self.consolidate_outlives_bound_spans( + predicate_span.shrink_to_lo(), + bounds, + bound_spans, + )); } + } - // If all predicates in where clause are inferable, drop the entire clause - // (including the `where`) - if generics.has_where_clause_predicates - && dropped_where_predicate_count == num_where_predicates - { - let where_span = generics.where_clause_span; - // Extend the where clause back to the closing `>` of the - // generics, except for tuple struct, which have the `where` - // after the fields of the struct. - let full_where_span = - if let hir::ItemKind::Struct(_, _, hir::VariantData::Tuple(..)) = item.kind { - where_span - } else { - generics.span.shrink_to_hi().to(where_span) - }; - - // Due to macro expansions, the `full_where_span` might not actually contain all - // predicates. - if where_lint_spans.iter().all(|&sp| full_where_span.contains(sp)) { - lint_spans.push(full_where_span); - } else { - lint_spans.extend(where_lint_spans); - } + // If all predicates in where clause are inferable, drop the entire clause + // (including the `where`) + if generics.has_where_clause_predicates + && dropped_where_predicate_count == num_where_predicates + { + let where_span = generics.where_clause_span; + let full_where_span = cx + .sess() + .source_map() + .span_extend_prev_while(where_span, |c| c.is_whitespace()) + .unwrap_or(where_span); + + // Due to macro expansions, the `full_where_span` might not actually contain all + // predicates. + if where_lint_spans.iter().all(|&sp| full_where_span.contains(sp)) { + lint_spans.push(full_where_span); } else { lint_spans.extend(where_lint_spans); } + } else { + lint_spans.extend(where_lint_spans); + } - if !lint_spans.is_empty() { - // Do not automatically delete outlives requirements from macros. - let applicability = if lint_spans.iter().all(|sp| sp.can_be_used_for_suggestions()) - { - Applicability::MachineApplicable - } else { - Applicability::MaybeIncorrect - }; + if !lint_spans.is_empty() { + // Do not automatically delete outlives requirements from macros. + let applicability = if lint_spans.iter().all(|sp| sp.can_be_used_for_suggestions()) { + Applicability::MachineApplicable + } else { + Applicability::MaybeIncorrect + }; - // Due to macros, there might be several predicates with the same span - // and we only want to suggest removing them once. - lint_spans.sort_unstable_by_key(|span| span.lo_hi()); - lint_spans.dedup_by_key(|span| span.lo_hi()); + // Due to macros, there might be several predicates with the same span + // and we only want to suggest removing them once. + lint_spans.sort_unstable_by_key(|span| span.lo_hi()); + lint_spans.dedup_by_key(|span| span.lo_hi()); - cx.emit_span_lint( - EXPLICIT_OUTLIVES_REQUIREMENTS, - lint_spans.clone(), - BuiltinExplicitOutlives { - suggestion: BuiltinExplicitOutlivesSuggestion { - spans: lint_spans, - applicability, - count: bound_count, - }, + cx.emit_span_lint( + EXPLICIT_OUTLIVES_REQUIREMENTS, + lint_spans.clone(), + BuiltinExplicitOutlives { + suggestion: BuiltinExplicitOutlivesSuggestion { + spans: lint_spans, + applicability, + count: bound_count, }, - ); - } + }, + ); } } } diff --git a/tests/ui/generic-const-items/lint-explicit-outlives-requirements.fixed b/tests/ui/generic-const-items/lint-explicit-outlives-requirements.fixed new file mode 100644 index 0000000000000..e1c884bc858ef --- /dev/null +++ b/tests/ui/generic-const-items/lint-explicit-outlives-requirements.fixed @@ -0,0 +1,14 @@ +// Test that lint `explicit_outlives_requirements` can also trigger on GCIs. + +//@ check-pass +//@ run-rustfix + +#![feature(generic_const_items)] +#![warn(explicit_outlives_requirements)] +#![allow(dead_code)] + +const C<'a, T>: Option<&'a T> = None; //~ WARN [explicit_outlives_requirements] + +const K<'a, T>: Option<&'a T> = None; //~ WARN [explicit_outlives_requirements] + +fn main() {} diff --git a/tests/ui/generic-const-items/lint-explicit-outlives-requirements.rs b/tests/ui/generic-const-items/lint-explicit-outlives-requirements.rs new file mode 100644 index 0000000000000..63b31ebcb3a0a --- /dev/null +++ b/tests/ui/generic-const-items/lint-explicit-outlives-requirements.rs @@ -0,0 +1,14 @@ +// Test that lint `explicit_outlives_requirements` can also trigger on GCIs. + +//@ check-pass +//@ run-rustfix + +#![feature(generic_const_items)] +#![warn(explicit_outlives_requirements)] +#![allow(dead_code)] + +const C<'a, T: 'a>: Option<&'a T> = None; //~ WARN [explicit_outlives_requirements] + +const K<'a, T>: Option<&'a T> = None where T: 'a; //~ WARN [explicit_outlives_requirements] + +fn main() {} diff --git a/tests/ui/generic-const-items/lint-explicit-outlives-requirements.stderr b/tests/ui/generic-const-items/lint-explicit-outlives-requirements.stderr new file mode 100644 index 0000000000000..420e8ff02c0de --- /dev/null +++ b/tests/ui/generic-const-items/lint-explicit-outlives-requirements.stderr @@ -0,0 +1,20 @@ +warning: outlives requirements can be inferred + --> $DIR/lint-explicit-outlives-requirements.rs:10:14 + | +LL | const C<'a, T: 'a>: Option<&'a T> = None; + | ^^^^ help: remove this bound + | +note: the lint level is defined here + --> $DIR/lint-explicit-outlives-requirements.rs:7:9 + | +LL | #![warn(explicit_outlives_requirements)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: outlives requirements can be inferred + --> $DIR/lint-explicit-outlives-requirements.rs:12:37 + | +LL | const K<'a, T>: Option<&'a T> = None where T: 'a; + | ^^^^^^^^^^^^ help: remove this bound + +warning: 2 warnings emitted + diff --git a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-macro.fixed b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-macro.fixed index 435857224ac31..1ed17ed4061e9 100644 --- a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-macro.fixed +++ b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-macro.fixed @@ -40,7 +40,7 @@ mod everything_inside { ('b: 'a) => { struct Foo<'a, 'b>(&'a &'b ()); //~^ ERROR: outlives requirements can be inferred - struct Bar<'a, 'b>(&'a &'b ()) ; + struct Bar<'a, 'b>(&'a &'b ()); //~^ ERROR: outlives requirements can be inferred struct Baz<'a, 'b>(&'a &'b ()) where (): Sized, ; //~^ ERROR: outlives requirements can be inferred @@ -54,7 +54,7 @@ mod inner_lifetime_outside_colon_inside { ($b:lifetime: 'a) => { struct Foo<'a, $b>(&'a &$b ()); //~^ ERROR: outlives requirements can be inferred - struct Bar<'a, $b>(&'a &$b ()) ; + struct Bar<'a, $b>(&'a &$b ()); //~^ ERROR: outlives requirements can be inferred struct Baz<'a, $b>(&'a &$b ()) where (): Sized, ; //~^ ERROR: outlives requirements can be inferred @@ -114,7 +114,7 @@ mod everything_outside_with_tt_outer { // FIXME: replacement span is corrupted due to a collision in metavar span table. // struct Foo<$a, $b $colon $a>(&$a &$b ()); // ^ ERROR: outlives requirements can be inferred - struct Bar<$a, $b>(&$a &$b ()) ; + struct Bar<$a, $b>(&$a &$b ()); //~^ ERROR: outlives requirements can be inferred struct Baz<$a, $b>(&$a &$b ()) where (): Sized, ; //~^ ERROR: outlives requirements can be inferred @@ -129,7 +129,7 @@ mod everything_outside_with_tt_both { // FIXME: replacement span is corrupted due to a collision in metavar span table. // struct Foo<$a, $b $colon $a>(&$a &$b ()); // ^ ERROR: outlives requirements can be inferred - struct Bar<$a, $b>(&$a &$b ()) ; + struct Bar<$a, $b>(&$a &$b ()); //~^ ERROR: outlives requirements can be inferred struct Baz<$a, $b>(&$a &$b ()) where (): Sized, ; //~^ ERROR: outlives requirements can be inferred diff --git a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-macro.stderr b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-macro.stderr index cda31ed346f28..55f462098b24f 100644 --- a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-macro.stderr +++ b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-macro.stderr @@ -28,12 +28,12 @@ LL | m!('b: 'a); | ---------- in this macro invocation error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives-macro.rs:43:44 + --> $DIR/edition-lint-infer-outlives-macro.rs:43:43 | LL | macro_rules! m { ... LL | struct Bar<'a, 'b>(&'a &'b ()) where 'b: 'a; - | ^^^^^^^^^^^^ help: remove this bound + | ^^^^^^^^^^^^^ help: remove this bound ... LL | m!('b: 'a); | ---------- in this macro invocation @@ -61,12 +61,12 @@ LL | m!('b: 'a); | ---------- in this macro invocation error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives-macro.rs:57:44 + --> $DIR/edition-lint-infer-outlives-macro.rs:57:43 | LL | macro_rules! m { ... LL | struct Bar<'a, $b>(&'a &$b ()) where $b: 'a; - | ^^^^^^^^^^^^ help: remove this bound + | ^^^^^^^^^^^^^ help: remove this bound ... LL | m!('b: 'a); | ---------- in this macro invocation @@ -83,12 +83,12 @@ LL | m!('b: 'a); | ---------- in this macro invocation error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives-macro.rs:117:44 + --> $DIR/edition-lint-infer-outlives-macro.rs:117:43 | LL | macro_rules! m { ... LL | struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; - | ^^^^^^^^^^^^^^^^^^ help: remove this bound + | ^^^^^^^^^^^^^^^^^^^ help: remove this bound ... LL | m!('b: 'a); | ---------- in this macro invocation @@ -105,12 +105,12 @@ LL | m!('b: 'a); | ---------- in this macro invocation error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives-macro.rs:132:44 + --> $DIR/edition-lint-infer-outlives-macro.rs:132:43 | LL | macro_rules! m { ... LL | struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; - | ^^^^^^^^^^^^^^^^^^ help: remove this bound + | ^^^^^^^^^^^^^^^^^^^ help: remove this bound ... LL | m!('b: 'a); | ---------- in this macro invocation diff --git a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-multispan.stderr b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-multispan.stderr index b2d4c3d035890..cfe01a6693426 100644 --- a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-multispan.stderr +++ b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives-multispan.stderr @@ -271,12 +271,12 @@ error: outlives requirements can be inferred --> $DIR/edition-lint-infer-outlives-multispan.rs:130:46 | LL | struct TeeOutlivesAyYooWhereBee<'a, 'b, T: 'a, U>(&'a T, &'b U) where U: 'b; - | ^^^^ ^^^^^^^^^^^ + | ^^^^ ^^^^^^^^^^^^ | help: remove these bounds | LL - struct TeeOutlivesAyYooWhereBee<'a, 'b, T: 'a, U>(&'a T, &'b U) where U: 'b; -LL + struct TeeOutlivesAyYooWhereBee<'a, 'b, T, U>(&'a T, &'b U) ; +LL + struct TeeOutlivesAyYooWhereBee<'a, 'b, T, U>(&'a T, &'b U); | error: outlives requirements can be inferred @@ -831,12 +831,12 @@ LL | m!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15); | --------------------------------------------------------- in this macro invocation error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives-multispan.rs:374:64 + --> $DIR/edition-lint-infer-outlives-multispan.rs:374:63 | LL | macro_rules! m { ... LL | struct FullWhere<'a, $($name,)+>(&'a ($($name,)+)) where $($name: 'a,)+; - | ^^^^^^^^^^^^^^^^^^ help: remove these bounds + | ^^^^^^^^^^^^^^^^^^^ help: remove these bounds ... LL | m!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15); | --------------------------------------------------------- in this macro invocation diff --git a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives.fixed b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives.fixed index fbf31764b6878..03a2699c4f027 100644 --- a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives.fixed +++ b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives.fixed @@ -253,7 +253,7 @@ mod tuple_structs { struct TeeIsDebugOutlivesAyBee<'a, 'b, T: Debug>(&'a &'b T); //~^ ERROR outlives requirements can be inferred - struct TeeWhereOutlivesAy<'a, T>(&'a T) ; + struct TeeWhereOutlivesAy<'a, T>(&'a T); //~^ ERROR outlives requirements can be inferred struct TeeWhereOutlivesAyIsDebug<'a, T>(&'a T) where T: Debug; @@ -262,7 +262,7 @@ mod tuple_structs { struct TeeWhereIsDebugOutlivesAy<'a, T>(&'a T) where T: Debug; //~^ ERROR outlives requirements can be inferred - struct TeeWhereOutlivesAyBee<'a, 'b, T>(&'a &'b T) ; + struct TeeWhereOutlivesAyBee<'a, 'b, T>(&'a &'b T); //~^ ERROR outlives requirements can be inferred struct TeeWhereOutlivesAyBeeIsDebug<'a, 'b, T>(&'a &'b T) where T: Debug; @@ -295,7 +295,7 @@ mod tuple_structs { struct TeeOutlivesAyBeeYooIsDebug<'a, 'b, T, U: Debug>(&'a &'b T, U); //~^ ERROR outlives requirements can be inferred - struct TeeYooWhereOutlivesAy<'a, T, U>(T, &'a U) ; + struct TeeYooWhereOutlivesAy<'a, T, U>(T, &'a U); //~^ ERROR outlives requirements can be inferred struct TeeYooWhereOutlivesAyIsDebug<'a, T, U>(T, &'a U) where U: Debug; @@ -307,7 +307,7 @@ mod tuple_structs { struct TeeOutlivesAyYooWhereIsDebug<'a, T, U>(&'a T, U) where U: Debug; //~^ ERROR outlives requirements can be inferred - struct TeeYooWhereOutlivesAyBee<'a, 'b, T, U>(T, &'a &'b U) ; + struct TeeYooWhereOutlivesAyBee<'a, 'b, T, U>(T, &'a &'b U); //~^ ERROR outlives requirements can be inferred struct TeeYooWhereOutlivesAyBeeIsDebug<'a, 'b, T, U>(T, &'a &'b U) where U: Debug; @@ -328,19 +328,19 @@ mod tuple_structs { struct BeeOutlivesAy<'a, 'b>(&'a &'b ()); //~^ ERROR outlives requirements can be inferred - struct BeeWhereOutlivesAy<'a, 'b>(&'a &'b ()) ; + struct BeeWhereOutlivesAy<'a, 'b>(&'a &'b ()); //~^ ERROR outlives requirements can be inferred struct BeeOutlivesAyTee<'a, 'b, T>(&'a &'b T); //~^ ERROR outlives requirements can be inferred - struct BeeWhereOutlivesAyTee<'a, 'b, T>(&'a &'b T) ; + struct BeeWhereOutlivesAyTee<'a, 'b, T>(&'a &'b T); //~^ ERROR outlives requirements can be inferred - struct BeeWhereOutlivesAyTeeWhereBee<'a, 'b, T>(&'a &'b T) ; + struct BeeWhereOutlivesAyTeeWhereBee<'a, 'b, T>(&'a &'b T); //~^ ERROR outlives requirements can be inferred - struct BeeWhereOutlivesAyTeeWhereAyBee<'a, 'b, T>(&'a &'b T) ; + struct BeeWhereOutlivesAyTeeWhereAyBee<'a, 'b, T>(&'a &'b T); //~^ ERROR outlives requirements can be inferred struct BeeOutlivesAyTeeDebug<'a, 'b, T: Debug>(&'a &'b T); diff --git a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives.stderr b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives.stderr index 472900b52d470..6911f46497c42 100644 --- a/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives.stderr +++ b/tests/ui/rust-2018/edition-lint-inter-outlives/edition-lint-infer-outlives.stderr @@ -283,10 +283,10 @@ LL | struct TeeIsDebugOutlivesAyBee<'a, 'b, T: Debug + 'a + 'b>(&'a &'b T); | ^^^^^^^^^^ help: remove these bounds error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives.rs:256:45 + --> $DIR/edition-lint-infer-outlives.rs:256:44 | LL | struct TeeWhereOutlivesAy<'a, T>(&'a T) where T: 'a; - | ^^^^^^^^^^^ help: remove this bound + | ^^^^^^^^^^^^ help: remove this bound error: outlives requirements can be inferred --> $DIR/edition-lint-infer-outlives.rs:259:61 @@ -301,10 +301,10 @@ LL | struct TeeWhereIsDebugOutlivesAy<'a, T>(&'a T) where T: Debug + 'a; | ^^^^^ help: remove this bound error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives.rs:265:56 + --> $DIR/edition-lint-infer-outlives.rs:265:55 | LL | struct TeeWhereOutlivesAyBee<'a, 'b, T>(&'a &'b T) where T: 'a + 'b; - | ^^^^^^^^^^^^^^^^ help: remove these bounds + | ^^^^^^^^^^^^^^^^^ help: remove these bounds error: outlives requirements can be inferred --> $DIR/edition-lint-infer-outlives.rs:268:72 @@ -367,10 +367,10 @@ LL | struct TeeOutlivesAyBeeYooIsDebug<'a, 'b, T: 'a + 'b, U: Debug>(&'a &'b | ^^^^^^^^^ help: remove these bounds error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives.rs:298:54 + --> $DIR/edition-lint-infer-outlives.rs:298:53 | LL | struct TeeYooWhereOutlivesAy<'a, T, U>(T, &'a U) where U: 'a; - | ^^^^^^^^^^^ help: remove this bound + | ^^^^^^^^^^^^ help: remove this bound error: outlives requirements can be inferred --> $DIR/edition-lint-infer-outlives.rs:301:70 @@ -391,10 +391,10 @@ LL | struct TeeOutlivesAyYooWhereIsDebug<'a, T: 'a, U>(&'a T, U) where U: De | ^^^^ help: remove this bound error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives.rs:310:65 + --> $DIR/edition-lint-infer-outlives.rs:310:64 | LL | struct TeeYooWhereOutlivesAyBee<'a, 'b, T, U>(T, &'a &'b U) where U: 'a + 'b; - | ^^^^^^^^^^^^^^^^ help: remove these bounds + | ^^^^^^^^^^^^^^^^^ help: remove these bounds error: outlives requirements can be inferred --> $DIR/edition-lint-infer-outlives.rs:313:81 @@ -433,10 +433,10 @@ LL | struct BeeOutlivesAy<'a, 'b: 'a>(&'a &'b ()); | ^^^^ help: remove this bound error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives.rs:331:51 + --> $DIR/edition-lint-infer-outlives.rs:331:50 | LL | struct BeeWhereOutlivesAy<'a, 'b>(&'a &'b ()) where 'b: 'a; - | ^^^^^^^^^^^^ help: remove this bound + | ^^^^^^^^^^^^^ help: remove this bound error: outlives requirements can be inferred --> $DIR/edition-lint-infer-outlives.rs:334:35 @@ -445,22 +445,22 @@ LL | struct BeeOutlivesAyTee<'a, 'b: 'a, T>(&'a &'b T); | ^^^^ help: remove this bound error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives.rs:337:56 + --> $DIR/edition-lint-infer-outlives.rs:337:55 | LL | struct BeeWhereOutlivesAyTee<'a, 'b, T>(&'a &'b T) where 'b: 'a; - | ^^^^^^^^^^^^ help: remove this bound + | ^^^^^^^^^^^^^ help: remove this bound error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives.rs:340:64 + --> $DIR/edition-lint-infer-outlives.rs:340:63 | LL | struct BeeWhereOutlivesAyTeeWhereBee<'a, 'b, T>(&'a &'b T) where 'b: 'a, T: 'b; - | ^^^^^^^^^^^^^^^^^^^ help: remove these bounds + | ^^^^^^^^^^^^^^^^^^^^ help: remove these bounds error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives.rs:343:66 + --> $DIR/edition-lint-infer-outlives.rs:343:65 | LL | struct BeeWhereOutlivesAyTeeWhereAyBee<'a, 'b, T>(&'a &'b T) where 'b: 'a, T: 'a + 'b; - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: remove these bounds + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove these bounds error: outlives requirements can be inferred --> $DIR/edition-lint-infer-outlives.rs:346:40