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.rs b/compiler/rustc_hir_analysis/src/collect.rs index 62b0e4d23dc43..49cb1078b814f 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1860,9 +1860,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/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs index 6439a3fb36b4d..b2a22d5bfdc83 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs @@ -173,8 +173,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/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 766b65215258a..ed25e3bb61302 100644 --- a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs +++ b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs @@ -1,274 +1,321 @@ -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( + let ty = tcx.type_of(field_def.did).instantiate_identity().skip_norm_wip(); + insert_required_outlives_clauses_to_be_wf( tcx, - field_ty, - field_span, - &global_inferred_outlives, + ty.into(), + 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( + 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), - &global_inferred_outlives, &mut item_required_clauses, - &mut explicit_map, + &global_inferred_clauses, + &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() => { + 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().into(), + span, + &mut item_required_clauses, + &global_inferred_clauses, + &mut global_explicit_clauses, + ); + 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, + ); + } + } _ => {} }; - // 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>, + arg: ty::GenericArg<'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, - }; - - 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"); - 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, - required_clauses, - ); - check_explicit_clauses( - tcx, - def.did(), - args.as_slice(), - required_clauses, - explicit_map, - 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, - required_clauses, - ); - check_explicit_clauses( - tcx, - def_id, - args.as_slice(), - required_clauses, - explicit_map, - 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.as_slice(), - required_clauses, - explicit_map, - 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"); - check_explicit_clauses( - tcx, - tcx.parent(def_id), - args.as_slice(), - required_clauses, - explicit_map, - IgnoreClausesReferencingSelf::No, - ); - } + 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, + ), + } + } +} - // FIXME(inherent_associated_types): Use the explicit clauses from the parent impl. - ty::Alias(_, ty::AliasTy { kind: ty::Inherent { .. }, .. }) => {} +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.as_slice(), + 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.as_slice(), + 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.as_slice(), + 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.as_slice(), + 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 { .. }, .. }) => {} + _ => {} + } +} - _ => {} +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.as_slice(), + 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.as_slice(), + required_clauses, + global_explicit_clauses, + IgnoreClausesReferencingSelf::No, + ); + } + _ => {} } } -/// Check the explicit clauses declared on the type. +/// Check the explicit clauses declared on the item. /// /// ### Example /// /// ```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); } } @@ -279,7 +326,7 @@ enum IgnoreClausesReferencingSelf { No, } -/// Check the inferred clauses of the type. +/// Check the inferred clauses of the item. /// /// ### Example /// @@ -293,28 +340,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::GenericArgs<'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..5afb7f38de4d4 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::Const => {} + 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_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index a376252ad1038..8f38e6f9a7de2 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1981,7 +1981,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { let generics = match item.kind { hir::ItemKind::Struct(_, generics, _) | hir::ItemKind::Enum(_, generics, _) - | hir::ItemKind::Union(_, generics, _) => generics, + | hir::ItemKind::Union(_, generics, _) + | hir::ItemKind::Const(_, generics, _, _) => generics, hir::ItemKind::TyAlias(_, generics, _) if cx.tcx.type_alias_is_checked(def_id) => { generics } diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 5ea7f10871061..a2d3f8ee23124 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 8de81ccd1e2fb..864955fbd5a06 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)]>, } 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/assumptions_on_binders/region_outlives_to_false.rs b/tests/ui/assumptions_on_binders/region_outlives_to_false.rs index d1d067a7bd6a8..1d9b0a97835f8 100644 --- a/tests/ui/assumptions_on_binders/region_outlives_to_false.rs +++ b/tests/ui/assumptions_on_binders/region_outlives_to_false.rs @@ -2,6 +2,9 @@ #![feature(generic_const_items)] -const FAIL<'a, T>: &'a T = todo!(); -//~^ ERROR: unable to satisfy constraints because it cannot be proven +trait Trait { + const FAIL<'a, T>: &'a T; + //~^ ERROR: unable to satisfy constraints because it cannot be proven +} + fn main() {} diff --git a/tests/ui/assumptions_on_binders/region_outlives_to_false.stderr b/tests/ui/assumptions_on_binders/region_outlives_to_false.stderr index d6e74acb3da98..eccd8b2f1a8f9 100644 --- a/tests/ui/assumptions_on_binders/region_outlives_to_false.stderr +++ b/tests/ui/assumptions_on_binders/region_outlives_to_false.stderr @@ -1,8 +1,8 @@ error: unable to satisfy constraints because it cannot be proven - --> $DIR/region_outlives_to_false.rs:5:20 + --> $DIR/region_outlives_to_false.rs:6:5 | -LL | const FAIL<'a, T>: &'a T = todo!(); - | ^^^^^ +LL | const FAIL<'a, T>: &'a T; + | ^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 1 previous error 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/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() {} 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/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`.