diff --git a/compiler/rustc_borrowck/src/constraints/mod.rs b/compiler/rustc_borrowck/src/constraints/mod.rs index a52269df68289..3dc73e23d69aa 100644 --- a/compiler/rustc_borrowck/src/constraints/mod.rs +++ b/compiler/rustc_borrowck/src/constraints/mod.rs @@ -1,15 +1,14 @@ use std::fmt; use std::ops::Index; +use rustc_data_structures::graph::scc; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::mir::ConstraintCategory; use rustc_middle::ty::{RegionVid, TyCtxt, VarianceDiagInfo}; use rustc_span::Span; -use tracing::{debug, instrument}; +use tracing::debug; -use crate::region_infer::{ConstraintSccs, RegionDefinition, RegionTracker}; use crate::type_check::Locations; -use crate::universal_regions::UniversalRegions; pub(crate) mod graph; @@ -57,105 +56,18 @@ impl<'tcx> OutlivesConstraintSet<'tcx> { /// Computes cycles (SCCs) in the graph of regions. In particular, /// find all regions R1, R2 such that R1: R2 and R2: R1 and group /// them into an SCC, and find the relationships between SCCs. - pub(crate) fn compute_sccs( + pub(crate) fn compute_sccs< + A: scc::Annotation, + AA: scc::Annotations, + >( &self, static_region: RegionVid, - definitions: &IndexVec>, - ) -> ConstraintSccs { - let constraint_graph = self.graph(definitions.len()); + num_region_vars: usize, + annotations: &mut AA, + ) -> scc::Sccs { + let constraint_graph = self.graph(num_region_vars); let region_graph = &constraint_graph.region_graph(self, static_region); - ConstraintSccs::new_with_annotation(®ion_graph, |r| { - RegionTracker::new(r, &definitions[r]) - }) - } - - /// This method handles Universe errors by rewriting the constraint - /// graph. For each strongly connected component in the constraint - /// graph such that there is a series of constraints - /// A: B: C: ... : X where - /// A's universe is smaller than X's and A is a placeholder, - /// add a constraint that A: 'static. This is a safe upper bound - /// in the face of borrow checker/trait solver limitations that will - /// eventually go away. - /// - /// For a more precise definition, see the documentation for - /// [`RegionTracker::has_incompatible_universes()`]. - /// - /// This edge case used to be handled during constraint propagation - /// by iterating over the strongly connected components in the constraint - /// graph while maintaining a set of bookkeeping mappings similar - /// to what is stored in `RegionTracker` and manually adding 'sttaic as - /// needed. - /// - /// It was rewritten as part of the Polonius project with the goal of moving - /// higher-kindedness concerns out of the path of the borrow checker, - /// for two reasons: - /// - /// 1. Implementing Polonius is difficult enough without also - /// handling them. - /// 2. The long-term goal is to handle higher-kinded concerns - /// in the trait solver, where they belong. This avoids - /// logic duplication and allows future trait solvers - /// to compute better bounds than for example our - /// "must outlive 'static" here. - /// - /// This code is a stop-gap measure in preparation for the future trait solver. - /// - /// Every constraint added by this method is an - /// internal `IllegalUniverse` constraint. - #[instrument(skip(self, universal_regions, definitions))] - pub(crate) fn add_outlives_static( - &mut self, - universal_regions: &UniversalRegions<'tcx>, - definitions: &IndexVec>, - ) -> ConstraintSccs { - let fr_static = universal_regions.fr_static; - let sccs = self.compute_sccs(fr_static, definitions); - - // Changed to `true` if we added any constraints to `self` and need to - // recompute SCCs. - let mut added_constraints = false; - - for scc in sccs.all_sccs() { - // No point in adding 'static: 'static! - // This micro-optimisation makes somewhat sense - // because static outlives *everything*. - if scc == sccs.scc(fr_static) { - continue; - } - - let annotation = sccs.annotation(scc); - - // If this SCC participates in a universe violation, - // e.g. if it reaches a region with a universe smaller than - // the largest region reached, add a requirement that it must - // outlive `'static`. - if annotation.has_incompatible_universes() { - // Optimisation opportunity: this will add more constraints than - // needed for correctness, since an SCC upstream of another with - // a universe violation will "infect" its downstream SCCs to also - // outlive static. - added_constraints = true; - let scc_representative_outlives_static = OutlivesConstraint { - sup: annotation.representative, - sub: fr_static, - category: ConstraintCategory::IllegalUniverse, - locations: Locations::All(rustc_span::DUMMY_SP), - span: rustc_span::DUMMY_SP, - variance_info: VarianceDiagInfo::None, - from_closure: false, - }; - self.push(scc_representative_outlives_static); - } - } - - if added_constraints { - // We changed the constraint set and so must recompute SCCs. - self.compute_sccs(fr_static, definitions) - } else { - // If we didn't add any back-edges; no more work needs doing - sccs - } + scc::Sccs::new_with_annotation(®ion_graph, annotations) } } diff --git a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs index 0de4bd67f0ce7..b69626491a230 100644 --- a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs @@ -24,7 +24,6 @@ use rustc_traits::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_ use tracing::{debug, instrument}; use crate::MirBorrowckCtxt; -use crate::region_infer::values::RegionElement; use crate::session_diagnostics::{ HigherRankedErrorCause, HigherRankedLifetimeError, HigherRankedSubtypeError, }; @@ -49,12 +48,13 @@ impl<'tcx> UniverseInfo<'tcx> { UniverseInfo::RelateTys { expected, found } } + /// Report an error where an element erroneously made its way into `placeholder`. pub(crate) fn report_erroneous_element( &self, mbcx: &mut MirBorrowckCtxt<'_, '_, 'tcx>, placeholder: ty::PlaceholderRegion, - error_element: RegionElement, cause: ObligationCause<'tcx>, + error_element: Option, ) { match *self { UniverseInfo::RelateTys { expected, found } => { @@ -68,7 +68,7 @@ impl<'tcx> UniverseInfo<'tcx> { mbcx.buffer_error(err); } UniverseInfo::TypeOp(ref type_op_info) => { - type_op_info.report_erroneous_element(mbcx, placeholder, error_element, cause); + type_op_info.report_erroneous_element(mbcx, placeholder, cause, error_element); } UniverseInfo::Other => { // FIXME: This error message isn't great, but it doesn't show @@ -145,52 +145,54 @@ pub(crate) trait TypeOpInfo<'tcx> { error_region: Option>, ) -> Option>; - /// Constraints require that `error_element` appear in the - /// values of `placeholder`, but this cannot be proven to - /// hold. Report an error. + /// Turn a placeholder region into a Region with its universe adjusted by + /// the base universe. + fn region_with_adjusted_universe( + &self, + placeholder: ty::PlaceholderRegion, + tcx: TyCtxt<'tcx>, + ) -> ty::Region<'tcx> { + let Some(adjusted_universe) = + placeholder.universe.as_u32().checked_sub(self.base_universe().as_u32()) + else { + unreachable!( + "Could not adjust universe {:?} of {placeholder:?} by base universe {:?}", + placeholder.universe, + self.base_universe() + ); + }; + ty::Region::new_placeholder( + tcx, + ty::Placeholder { universe: adjusted_universe.into(), bound: placeholder.bound }, + ) + } + + /// Report an error where an erroneous element reaches `placeholder`. + /// The erroneous element is either another placeholder that we provide, + /// or we figure out what happened later. #[instrument(level = "debug", skip(self, mbcx))] fn report_erroneous_element( &self, mbcx: &mut MirBorrowckCtxt<'_, '_, 'tcx>, placeholder: ty::PlaceholderRegion, - error_element: RegionElement, cause: ObligationCause<'tcx>, + error_element: Option, ) { let tcx = mbcx.infcx.tcx; - let base_universe = self.base_universe(); - debug!(?base_universe); - - let Some(adjusted_universe) = - placeholder.universe.as_u32().checked_sub(base_universe.as_u32()) - else { - mbcx.buffer_error(self.fallback_error(tcx, cause.span)); - return; - }; - let placeholder_region = ty::Region::new_placeholder( - tcx, - ty::Placeholder { universe: adjusted_universe.into(), bound: placeholder.bound }, - ); - - let error_region = if let RegionElement::PlaceholderRegion(error_placeholder) = - error_element - { - let adjusted_universe = - error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32()); - adjusted_universe.map(|adjusted| { - ty::Region::new_placeholder( - tcx, - ty::Placeholder { universe: adjusted.into(), bound: error_placeholder.bound }, - ) - }) - } else { - None - }; + // FIXME: these adjusted universes are not (always) the same ones as we compute + // earlier. They probably should be, but the logic downstream is complicated, + // and assumes they use whatever this is. + // + // In fact, this function throws away a lot of interesting information that would + // probably allow bypassing lots of logic downstream for a much simpler flow. + let placeholder_region = self.region_with_adjusted_universe(placeholder, tcx); + let error_element = error_element.map(|e| self.region_with_adjusted_universe(e, tcx)); debug!(?placeholder_region); let span = cause.span; - let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_region); + let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_element); debug!(?nice_error); mbcx.buffer_error(nice_error.unwrap_or_else(|| self.fallback_error(tcx, span))); diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs index a845431facac1..856b1516ce772 100644 --- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs +++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs @@ -565,7 +565,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { let (blame_constraint, path) = self.regioncx.best_blame_constraint( borrow_region, NllRegionVariableOrigin::FreeRegion, - |r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region), + outlived_region, ); let BlameConstraint { category, from_closure, cause, .. } = blame_constraint; diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs index 7192a889adcbc..494c6d288da77 100644 --- a/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs +++ b/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs @@ -174,10 +174,9 @@ impl<'tcx> TypeVisitor> for FindOpaqueRegion<'_, 'tcx> { let opaque_region_vid = self.regioncx.to_region_vid(opaque_region); // Find a path between the borrow region and our opaque capture. - if let Some((path, _)) = - self.regioncx.find_constraint_paths_between_regions(self.borrow_region, |r| { - r == opaque_region_vid - }) + if let Some((path, _)) = self + .regioncx + .constraint_path_between_regions(self.borrow_region, opaque_region_vid) { for constraint in path { // If we find a call in this path, then check if it defines the opaque. diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index 3bec07afa0fe0..2776843e933bb 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -10,6 +10,7 @@ use rustc_hir::def::Res::Def; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::VisitorExt; use rustc_hir::{PolyTraitRef, TyKind, WhereBoundPredicate}; +use rustc_infer::infer::region_constraints::GenericKind; use rustc_infer::infer::{NllRegionVariableOrigin, RelateParamBound}; use rustc_middle::bug; use rustc_middle::hir::place::PlaceBase; @@ -30,8 +31,8 @@ use tracing::{debug, instrument, trace}; use super::{OutlivesSuggestionBuilder, RegionName, RegionNameSource}; use crate::nll::ConstraintDescription; +use crate::region_infer::BlameConstraint; use crate::region_infer::values::RegionElement; -use crate::region_infer::{BlameConstraint, TypeTest}; use crate::session_diagnostics::{ FnMutError, FnMutReturnTypeErr, GenericDoesNotLiveLongEnough, LifetimeOutliveErr, LifetimeReturnCategoryErr, RequireStaticErr, VarHereDenote, @@ -62,7 +63,7 @@ impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> { | ConstraintCategory::Boring | ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal - | ConstraintCategory::IllegalUniverse => "", + | ConstraintCategory::IllegalPlaceholder(..) => "", } } } @@ -105,10 +106,28 @@ impl std::fmt::Debug for RegionErrors<'_> { #[derive(Clone, Debug)] pub(crate) enum RegionErrorKind<'tcx> { - /// A generic bound failure for a type test (`T: 'a`). - TypeTestError { type_test: TypeTest<'tcx> }, + /// 'a outlives 'b, and both are placeholders. + PlaceholderMismatch { + rvid_a: RegionVid, + rvid_b: RegionVid, + origin_a: ty::PlaceholderRegion, + origin_b: ty::PlaceholderRegion, + }, + + /// A generic bound failure for a type test (`T: 'a`). If placeholder leaks caused `'a: 'static`, + /// implying `T: 'static`, `static_due_to_placeholders` is `true`, otherwise it is `false`. + TypeTestError { + lower_bound: RegionVid, + span: Span, + generic_kind: GenericKind<'tcx>, + failed_due_to_placeholders: bool, + }, /// An unexpected hidden region for an opaque type. + /// This is a more specific variant of `UnexpectedHiddenRegion`, + /// for cases where the mismatched region isn't a hidden region + /// from an opaque type, but rather any other unexpected, invalid, + /// or undeclared element. UnexpectedHiddenRegion { /// The span for the member constraint. span: Span, @@ -142,6 +161,19 @@ pub(crate) enum RegionErrorKind<'tcx> { /// encountered and leave the rest unreported so as not to overwhelm the user. is_reported: bool, }, + /// Indicates that a placeholder has a universe too large for one + /// of its member existentials, or, equivalently, that there is + /// a path through the outlives constraint graph from a placeholder + /// to an existential region that cannot name it. + PlaceholderReachesExistentialThatCannotNameIt { + /// the placeholder that transitively outlives an + /// existential that shouldn't leak into it + longer_fr: RegionVid, + /// The existential leaking into `longer_fr`. + existental_that_cannot_name_longer: RegionVid, + // `longer_fr`'s originating placeholder region. + placeholder: ty::PlaceholderRegion, + }, } /// Information about the various region constraints involved in a borrow checker error. @@ -171,17 +203,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// `to_error_region`. pub(super) fn to_error_region_vid(&self, r: RegionVid) -> Option { if self.regioncx.universal_regions().is_universal_region(r) { - Some(r) - } else { - // We just want something nameable, even if it's not - // actually an upper bound. - let upper_bound = self.regioncx.approx_universal_upper_bound(r); + return Some(r); + } + // We just want something nameable, even if it's not + // actually an upper bound. + let upper_bound = self.regioncx.approx_universal_upper_bound(r); - if self.regioncx.upper_bound_in_region_scc(r, upper_bound) { - self.to_error_region_vid(upper_bound) - } else { - None - } + if self.regioncx.upper_bound_in_region_scc(r, upper_bound) { + self.to_error_region_vid(upper_bound) + } else { + None } } @@ -191,7 +222,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { T: TypeFoldable>, { fold_regions(tcx, ty, |region, _| match region.kind() { - ty::ReVar(vid) => self.to_error_region(vid).unwrap_or(region), + ty::ReVar(vid) => self.regioncx.first_named_region_reached(vid).unwrap_or(region), _ => region, }) } @@ -217,49 +248,39 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { &self, diag: &mut Diag<'_>, lower_bound: RegionVid, - ) { + ) -> Option<()> { let mut suggestions = vec![]; let tcx = self.infcx.tcx; - // find generic associated types in the given region 'lower_bound' - let gat_id_and_generics = self - .regioncx - .placeholders_contained_in(lower_bound) - .map(|placeholder| { - if let Some(id) = placeholder.bound.kind.get_id() - && let Some(placeholder_id) = id.as_local() - && let gat_hir_id = tcx.local_def_id_to_hir_id(placeholder_id) - && let Some(generics_impl) = - tcx.parent_hir_node(tcx.parent_hir_id(gat_hir_id)).generics() - { - Some((gat_hir_id, generics_impl)) - } else { - None - } - }) - .collect::>(); - debug!(?gat_id_and_generics); - // find higher-ranked trait bounds bounded to the generic associated types + let scc = self.regioncx.constraint_sccs().scc(lower_bound); + + let placeholder: ty::PlaceholderRegion = self.regioncx.placeholder_representative(scc)?; + + let placeholder_id = placeholder.bound.kind.get_id()?.as_local()?; + let gat_hir_id = self.infcx.tcx.local_def_id_to_hir_id(placeholder_id); + let generics_impl = + self.infcx.tcx.parent_hir_node(self.infcx.tcx.parent_hir_id(gat_hir_id)).generics()?; + let mut hrtb_bounds = vec![]; - gat_id_and_generics.iter().flatten().for_each(|(gat_hir_id, generics)| { - for pred in generics.predicates { - let BoundPredicate(WhereBoundPredicate { bound_generic_params, bounds, .. }) = - pred.kind - else { - continue; - }; - if bound_generic_params - .iter() - .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == *gat_hir_id) - .is_some() - { - for bound in *bounds { - hrtb_bounds.push(bound); - } + + for pred in generics_impl.predicates { + let BoundPredicate(WhereBoundPredicate { bound_generic_params, bounds, .. }) = + pred.kind + else { + continue; + }; + if bound_generic_params + .iter() + .rfind(|bgp| self.infcx.tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id) + .is_some() + { + for bound in *bounds { + hrtb_bounds.push(bound); } } - }); + } + debug!(?hrtb_bounds); hrtb_bounds.iter().for_each(|bound| { @@ -304,6 +325,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { Applicability::MaybeIncorrect, ); } + Some(()) } /// Produces nice borrowck error diagnostics for all the errors collected in `nll_errors`. @@ -315,55 +337,76 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> = None; + let emit_generic_does_not_live_long_enough = + |cx: &mut Self, generic_kind: GenericKind<'_>, span: Span, lower_bound: RegionVid| { + // FIXME. We should handle this case better. It + // indicates that we have e.g., some region variable + // whose value is like `'a+'b` where `'a` and `'b` are + // distinct unrelated universal regions that are not + // known to outlive one another. It'd be nice to have + // some examples where this arises to decide how best + // to report it; we could probably handle it by + // iterating over the universal regions and reporting + // an error that multiple bounds are required. + let mut diag = cx.dcx().create_err(GenericDoesNotLiveLongEnough { + kind: generic_kind.to_string(), + span, + }); + + // Add notes and suggestions for the case of 'static lifetime + // implied but not specified when a generic associated types + // are from higher-ranked trait bounds + cx.suggest_static_lifetime_for_gat_from_hrtb(&mut diag, lower_bound); + + cx.buffer_error(diag); + }; + for (nll_error, _) in nll_errors.into_iter() { match nll_error { - RegionErrorKind::TypeTestError { type_test } => { + RegionErrorKind::TypeTestError { + generic_kind, + lower_bound, + span, + failed_due_to_placeholders: true, + } => emit_generic_does_not_live_long_enough(self, generic_kind, span, lower_bound), + RegionErrorKind::TypeTestError { + lower_bound, + span, + generic_kind, + failed_due_to_placeholders: false, + } => { // Try to convert the lower-bound region into something named we can print for // the user. - let lower_bound_region = self.to_error_region(type_test.lower_bound); - - let type_test_span = type_test.span; - - if let Some(lower_bound_region) = lower_bound_region { - let generic_ty = self.name_regions( - self.infcx.tcx, - type_test.generic_kind.to_ty(self.infcx.tcx), + let Some(lower_bound_region) = self.to_error_region(lower_bound) else { + // FIXME: this can possibly never happen along this error branch. It's possible + // (and the case in the UI tests) that any lower_bound that's an internal region + // and not something we want in error output and fails the type-test + // evaluation would have been triggered by a higher-ranked misfire that would have + // set `failed_due_to_placeholders` to `true`. The previous code structure + // before elimination of placeholders in borrowck suggests this, since this + // failure was used to trigger related diagnostics. If you think you can figure this out, + // either add a counterexample where this branch is taken and remove this comment, + // or replace this with an `unreachable!()` or similar and describe why. + emit_generic_does_not_live_long_enough( + self, + generic_kind, + span, + lower_bound, ); - let origin = RelateParamBound(type_test_span, generic_ty, None); - self.buffer_error(self.infcx.err_ctxt().construct_generic_bound_failure( - self.body.source.def_id().expect_local(), - type_test_span, - Some(origin), - self.name_regions(self.infcx.tcx, type_test.generic_kind), - lower_bound_region, - )); - } else { - // FIXME. We should handle this case better. It - // indicates that we have e.g., some region variable - // whose value is like `'a+'b` where `'a` and `'b` are - // distinct unrelated universal regions that are not - // known to outlive one another. It'd be nice to have - // some examples where this arises to decide how best - // to report it; we could probably handle it by - // iterating over the universal regions and reporting - // an error that multiple bounds are required. - let mut diag = self.dcx().create_err(GenericDoesNotLiveLongEnough { - kind: type_test.generic_kind.to_string(), - span: type_test_span, - }); - - // Add notes and suggestions for the case of 'static lifetime - // implied but not specified when a generic associated types - // are from higher-ranked trait bounds - self.suggest_static_lifetime_for_gat_from_hrtb( - &mut diag, - type_test.lower_bound, - ); - - self.buffer_error(diag); - } + continue; + }; + debug!(?lower_bound_region); + let generic_ty = + self.name_regions(self.infcx.tcx, generic_kind.to_ty(self.infcx.tcx)); + let origin = RelateParamBound(span, generic_ty, None); + self.buffer_error(self.infcx.err_ctxt().construct_generic_bound_failure( + self.body.source.def_id().expect_local(), + span, + Some(origin), + self.name_regions(self.infcx.tcx, generic_kind), + lower_bound_region, + )); } - RegionErrorKind::UnexpectedHiddenRegion { span, hidden_ty, key, member_region } => { let named_ty = self.regioncx.name_regions_for_member_constraint(self.infcx.tcx, hidden_ty); @@ -387,7 +430,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { diag.delay_as_bug(); } } - RegionErrorKind::BoundUniversalRegionError { longer_fr, placeholder, @@ -395,19 +437,33 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } => { let error_vid = self.regioncx.region_from_element(longer_fr, &error_element); - // Find the code to blame for the fact that `longer_fr` outlives `error_fr`. - let (_, cause) = self.regioncx.find_outlives_blame_span( + self.report_erroneous_rvid_reaches_placeholder( longer_fr, - NllRegionVariableOrigin::Placeholder(placeholder), + placeholder, error_vid, ); + } + RegionErrorKind::PlaceholderMismatch { rvid_a, rvid_b, origin_a, origin_b } => { + debug!( + "Placeholder mismatch: {rvid_a:?} ({origin_a:?}) reaches {rvid_b:?} ({origin_b:?})" + ); - let universe = placeholder.universe; - let universe_info = self.regioncx.universe_info(universe); + let (_, cause) = self.regioncx.find_outlives_blame_span( + rvid_a, + NllRegionVariableOrigin::Placeholder(origin_a), + rvid_b, + ); - universe_info.report_erroneous_element(self, placeholder, error_element, cause); + // FIXME We may be able to shorten the code path here, and immediately + // report a `RegionResolutionError::UpperBoundUniverseConflict`, but + // that's left for a future refactoring. + self.regioncx.universe_info(origin_a.universe).report_erroneous_element( + self, + origin_a, + cause, + Some(origin_b), + ); } - RegionErrorKind::RegionError { fr_origin, longer_fr, shorter_fr, is_reported } => { if is_reported { self.report_region_error( @@ -429,6 +485,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ); } } + RegionErrorKind::PlaceholderReachesExistentialThatCannotNameIt { + longer_fr, + existental_that_cannot_name_longer, + placeholder, + } => self.report_erroneous_rvid_reaches_placeholder( + longer_fr, + placeholder, + existental_that_cannot_name_longer, + ), } } @@ -456,9 +521,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ) { debug!("report_region_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr); - let (blame_constraint, path) = self.regioncx.best_blame_constraint(fr, fr_origin, |r| { - self.regioncx.provides_universal_region(r, fr, outlived_fr) - }); + let (blame_constraint, path) = + self.regioncx.best_blame_constraint(fr, fr_origin, outlived_fr); let BlameConstraint { category, cause, variance_info, .. } = blame_constraint; debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info); diff --git a/compiler/rustc_borrowck/src/eliminate_placeholders.rs b/compiler/rustc_borrowck/src/eliminate_placeholders.rs new file mode 100644 index 0000000000000..e17ccdd925fbe --- /dev/null +++ b/compiler/rustc_borrowck/src/eliminate_placeholders.rs @@ -0,0 +1,941 @@ +//! Logic for lowering higher-kinded outlives constraints +//! (with placeholders and universes) and turn them into regular +//! outlives constraints. +//! +//! This logic is provisional and should be removed once the trait +//! solver can handle this kind of constraint. + +use std::collections::VecDeque; + +use rustc_data_structures::frozen::Frozen; +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::graph::scc::{self, Sccs}; +use rustc_index::IndexVec; +use rustc_infer::infer::NllRegionVariableOrigin; +use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound}; +use rustc_infer::infer::relate::TypeRelation; +use rustc_middle::bug; +use rustc_middle::ty::relate::{self, Relate, RelateResult}; +use rustc_middle::ty::{self, Region, RegionVid, Ty, TyCtxt, UniverseIndex}; +use tracing::{debug, instrument, trace}; + +use crate::constraints::graph::{ConstraintGraph, Normal, RegionGraph}; +use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet}; +use crate::consumers::OutlivesConstraint; +use crate::diagnostics::{RegionErrorKind, RegionErrors}; +use crate::member_constraints::MemberConstraintSet; +use crate::region_infer::{RegionDefinition, Representative, TypeTest, TypeTestOrigin}; +use crate::ty::VarianceDiagInfo; +use crate::type_check::Locations; +use crate::universal_regions::UniversalRegions; +use crate::{BorrowckInferCtxt, ConstraintCategory}; + +/// A set of outlives constraints after rewriting to remove +/// higher-kinded constraints. +pub(crate) struct LoweredConstraints<'tcx> { + pub(crate) type_tests: Vec>, + pub(crate) sccs: Sccs, + pub(crate) definitions: Frozen>>, + pub(crate) scc_representatives: IndexVec, + pub(crate) member_constraints: MemberConstraintSet<'tcx, ConstraintSccIndex>, + pub(crate) outlives_constraints: OutlivesConstraintSet<'tcx>, +} + +pub(crate) struct SccAnnotations<'d, 'tcx, A: scc::Annotation> { + pub(crate) scc_to_annotation: IndexVec, + definitions: &'d IndexVec>, +} + +impl<'d, 'tcx, A: scc::Annotation> SccAnnotations<'d, 'tcx, A> { + pub(crate) fn init(definitions: &'d IndexVec>) -> Self { + Self { scc_to_annotation: IndexVec::new(), definitions } + } +} + +#[derive(Copy, Debug, Clone, PartialEq, Eq)] +enum PlaceholderReachability { + /// This SCC reaches no placeholders. + NoPlaceholders, + /// This SCC reaches at least one placeholder. + Placeholders { + /// The largest-universed placeholder we can reach + max_universe: (UniverseIndex, RegionVid), + + /// The placeholder with the smallest ID + min_placeholder: RegionVid, + + /// The placeholder with the largest ID + max_placeholder: RegionVid, + }, +} + +impl PlaceholderReachability { + fn merge(self, other: PlaceholderReachability) -> PlaceholderReachability { + use PlaceholderReachability::*; + match (self, other) { + (NoPlaceholders, NoPlaceholders) => NoPlaceholders, + (NoPlaceholders, p @ Placeholders { .. }) + | (p @ Placeholders { .. }, NoPlaceholders) => p, + ( + Placeholders { + min_placeholder: min_pl, + max_placeholder: max_pl, + max_universe: max_u, + }, + Placeholders { min_placeholder, max_placeholder, max_universe }, + ) => Placeholders { + min_placeholder: core::cmp::min(min_pl, min_placeholder), + max_placeholder: core::cmp::max(max_pl, max_placeholder), + max_universe: core::cmp::max(max_u, max_universe), + }, + } + } + + /// If we have reached placeholders, determine if they can + /// be named from this universe. + fn can_be_named_by(&self, from: UniverseIndex) -> bool { + if let PlaceholderReachability::Placeholders { max_universe: (max_universe, _), .. } = self + { + from.can_name(*max_universe) + } else { + true // No placeholders, no problems. + } + } +} + +/// An annotation for region graph SCCs that tracks +/// the values of its elements and properties of +/// SCCs reached from them. +#[derive(Copy, Debug, Clone)] +struct RegionTracker { + /// The representative Region Variable Id for this SCC. + representative: Representative, + + /// The smallest universe reachable (and its region) + min_universe: (UniverseIndex, RegionVid), + + // Metadata about reachable placeholders + reachable_placeholders: PlaceholderReachability, + + /// Track the existential with the smallest universe we reach. + /// For existentials, the assigned universe corresponds to the + /// largest universed placeholder they are allowed to end up in. + /// + /// In other words, this tracks the smallest maximum (hardest constraint) + /// of any existential this SCC reaches, and the rvid of the existential + /// that brought it. + min_universe_reachable_existential: Option<(UniverseIndex, RegionVid)>, +} + +impl scc::Annotation for RegionTracker { + fn merge_scc(self, other: Self) -> Self { + trace!("{:?} << {:?}", self.representative, other.representative); + let min_universe = if other.min_universe.0 < self.min_universe.0 { + other.min_universe + } else { + self.min_universe + }; + + let min_universe_reachable_existential = smallest_reachable_existential( + self.min_universe_reachable_existential, + other.min_universe_reachable_existential, + ); + Self { + reachable_placeholders: self.reachable_placeholders.merge(other.reachable_placeholders), + min_universe, + representative: self.representative.merge_scc(other.representative), + min_universe_reachable_existential, + } + } + + fn merge_reached(mut self, other: Self) -> Self { + self.min_universe_reachable_existential = smallest_reachable_existential( + self.min_universe_reachable_existential, + other.min_universe_reachable_existential, + ); + + // This detail is subtle. We stop early here, because there may be multiple + // illegally reached universes, but they are not equally good as blame candidates. + // In general, the ones with the smallest indices of their RegionVids will + // be the best ones, and those will also be visited first. This code + // then will suptly prefer a universe violation happening close from where the + // constraint graph walk started over one that happens later. + // FIXME: a potential optimisation if this is slow is to reimplement + // this check as a boolean fuse, since it will idempotently turn + // true once triggered and never go false again. + if self.reaches_too_large_universe().is_some() { + debug!("SCC already has a placeholder violation; no use looking for more!"); + self + } else { + self.reachable_placeholders = + self.reachable_placeholders.merge(other.reachable_placeholders); + self + } + } +} + +/// Select the worst universe-constrained of two existentials. +fn smallest_reachable_existential( + min_universe_reachable_existential_1: Option<(UniverseIndex, RegionVid)>, + min_universe_reachable_existential_2: Option<(UniverseIndex, RegionVid)>, +) -> Option<(UniverseIndex, RegionVid)> { + // Note: this will prefer a small region vid over a large one. That's generally + // good, but this probably does not affect the outcome. It might affect diagnostics + // in the future. + match (min_universe_reachable_existential_1, min_universe_reachable_existential_2) { + (Some(a), Some(b)) => Some(std::cmp::min(a, b)), + (a, b) => a.or(b), + } +} + +impl RegionTracker { + fn new(representative: RegionVid, definition: &RegionDefinition<'_>) -> Self { + let universe_and_rvid = (definition.universe, representative); + let (representative, reachable_placeholders, min_universe_reachable_existential) = { + match definition.origin { + NllRegionVariableOrigin::FreeRegion => ( + Representative::FreeRegion(representative), + PlaceholderReachability::NoPlaceholders, + None, + ), + NllRegionVariableOrigin::Placeholder(_) => ( + Representative::Placeholder(representative), + PlaceholderReachability::Placeholders { + max_universe: universe_and_rvid, + min_placeholder: representative, + max_placeholder: representative, + }, + None, + ), + NllRegionVariableOrigin::Existential { .. } => ( + Representative::Existential(representative), + PlaceholderReachability::NoPlaceholders, + Some((definition.universe, representative)), + ), + } + }; + Self { + representative, + min_universe: universe_and_rvid, + reachable_placeholders, + min_universe_reachable_existential, + } + } + + /// The smallest-indexed universe reachable from and/or in this SCC. + fn min_universe(self) -> UniverseIndex { + self.min_universe.0 + } + + /// Determine if this SCC reaches a placeholder that isn't `placeholder_rvid`, + /// returning it if that is the case. This prefers the placeholder with the + /// smallest region variable ID. + fn reaches_other_placeholder(&self, placeholder_rvid: RegionVid) -> Option { + match self.reachable_placeholders { + PlaceholderReachability::NoPlaceholders => None, + PlaceholderReachability::Placeholders { min_placeholder, max_placeholder, .. } + if min_placeholder == max_placeholder => + { + None + } + PlaceholderReachability::Placeholders { min_placeholder, max_placeholder, .. } + if min_placeholder == placeholder_rvid => + { + Some(max_placeholder) + } + PlaceholderReachability::Placeholders { min_placeholder, .. } => Some(min_placeholder), + } + } + + /// Determine if the tracked universes of the two SCCs + /// are compatible. + fn universe_compatible_with(&self, other: RegionTracker) -> bool { + self.min_universe().can_name(other.min_universe()) + || other.reachable_placeholders.can_be_named_by(self.min_universe()) + } + + fn representative_rvid(&self) -> RegionVid { + self.representative.rvid() + } + + fn into_representative(self) -> Representative { + self.representative + } + + /// Determine if this SCC is in the root universe. + fn in_root_universe(self) -> bool { + self.min_universe() == UniverseIndex::ROOT + } + + /// If this SCC reaches an universe that's too large, return it. + fn reaches_too_large_universe(&self) -> Option<(RegionVid, UniverseIndex)> { + let min_u = self.min_universe(); + + let PlaceholderReachability::Placeholders { max_universe: (max_u, max_u_rvid), .. } = + self.reachable_placeholders + else { + return None; + }; + + if min_u.can_name(max_u) { + return None; + } + + Some((max_u_rvid, max_u)) + } + + /// Check for the second and final type of placeholder leak, + /// where an existential `'e` outlives (transitively) a placeholder `p` + /// and `e` cannot name `p`. + /// + /// Returns *a* culprit (though there may be more than one). + fn reaches_existential_that_cannot_name_us(&self) -> Option { + let (min_u, min_rvid) = self.min_universe_reachable_existential?; + (min_u < self.min_universe()).then_some(min_rvid) + } +} + +impl scc::Annotations + for SccAnnotations<'_, '_, RegionTracker> +{ + fn new(&self, element: RegionVid) -> RegionTracker { + RegionTracker::new(element, &self.definitions[element]) + } + + fn annotate_scc(&mut self, scc: ConstraintSccIndex, annotation: RegionTracker) { + let idx = self.scc_to_annotation.push(annotation); + assert!(idx == scc); + } +} + +impl scc::Annotations + for SccAnnotations<'_, '_, Representative> +{ + fn new(&self, element: RegionVid) -> Representative { + Representative::new(element, &self.definitions) + } + + fn annotate_scc(&mut self, scc: ConstraintSccIndex, annotation: Representative) { + let idx = self.scc_to_annotation.push(annotation); + assert!(idx == scc); + } +} + +/// Identify errors where placeholders illegally reach other regions, and generate +/// errors stored into `errors_buffer`. +/// +/// There are two sources of such errors: +/// 1. A placeholder reaches (possibly transitively) another placeholder. +/// 2. A placeholder `p` reaches (possibly transitively) an existential `e`, +/// where `e` has an allowed maximum universe smaller than `p`'s. +/// +/// There are other potential placeholder errors, but those are detected after +/// region inference, since it may apply type tests or member constraints that +/// alter the contents of SCCs and thus can't be detected at this point. +#[instrument(skip(definitions, sccs, annotations, errors_buffer), level = "debug")] +fn find_placeholder_mismatch_errors<'tcx>( + definitions: &IndexVec>, + sccs: &Sccs, + annotations: &SccAnnotations<'_, '_, RegionTracker>, + errors_buffer: &mut RegionErrors<'tcx>, +) { + use NllRegionVariableOrigin::Placeholder; + for (rvid, definition) in definitions.iter_enumerated() { + let Placeholder(origin_a) = definition.origin else { + continue; + }; + + let scc = sccs.scc(rvid); + let annotation = annotations.scc_to_annotation[scc]; + + if let Some(existental_that_cannot_name_rvid) = + annotation.reaches_existential_that_cannot_name_us() + { + errors_buffer.push(RegionErrorKind::PlaceholderReachesExistentialThatCannotNameIt { + longer_fr: rvid, + existental_that_cannot_name_longer: existental_that_cannot_name_rvid, + placeholder: origin_a, + }) + } + + let Some(other_placeholder) = annotation.reaches_other_placeholder(rvid) else { + trace!("{rvid:?} reaches no other placeholders"); + continue; + }; + + debug!( + "Placeholder {rvid:?} of SCC {scc:?} reaches other placeholder {other_placeholder:?}" + ); + + // FIXME SURELY there is a neater way to do this? + let Placeholder(origin_b) = definitions[other_placeholder].origin else { + unreachable!( + "Region {rvid:?}, {other_placeholder:?} should be placeholders but aren't!" + ); + }; + + errors_buffer.push(RegionErrorKind::PlaceholderMismatch { + rvid_a: rvid, + rvid_b: other_placeholder, + origin_a, + origin_b, + }); + } +} + +/// Determines if the region variable definitions contain +/// placeholers, and compute them for later use. +fn region_definitions<'tcx>( + universal_regions: &UniversalRegions<'tcx>, + infcx: &BorrowckInferCtxt<'tcx>, +) -> (Frozen>>, bool) { + let var_infos = infcx.get_region_var_infos(); + // Create a RegionDefinition for each inference variable. This happens here because + // it allows us to sneak in a cheap check for placeholders. Otherwise, its proper home + // is in `RegionInferenceContext::new()`, probably. + let mut definitions = IndexVec::with_capacity(var_infos.len()); + let mut has_placeholders = false; + + for info in var_infos.iter() { + let definition = RegionDefinition::new(info); + has_placeholders |= matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)); + definitions.push(definition); + } + + // Add external names from universal regions in fun function definitions. + for (external_name, variable) in universal_regions.named_universal_regions_iter() { + debug!("region {:?} has external name {:?}", variable, external_name); + definitions[variable].external_name = Some(external_name); + } + (Frozen::freeze(definitions), has_placeholders) +} + +/// This method handles Universe errors by rewriting the constraint +/// graph. For each strongly connected component in the constraint +/// graph such that there is a series of constraints +/// A: B: C: ... : X where +/// A's universe is smaller than X's and A is a placeholder, +/// add a constraint that A: 'static. This is a safe upper bound +/// in the face of borrow checker/trait solver limitations that will +/// eventually go away. +/// +/// For a more precise definition, see the documentation for +/// [`RegionTracker`] and its methods!. +/// +/// Since universes can also be involved in errors (if one placeholder +/// transitively outlives another), this function also flags those. +/// +/// Additionally, it similarly rewrites type-tests. +/// +/// This edge case used to be handled during constraint propagation +/// by iterating over the strongly connected components in the constraint +/// graph while maintaining a set of bookkeeping mappings similar +/// to what is stored in `RegionTracker` and manually adding 'sttaic as +/// needed. +/// +/// It was rewritten as part of the Polonius project with the goal of moving +/// higher-kindedness concerns out of the path of the borrow checker, +/// for two reasons: +/// +/// 1. Implementing Polonius is difficult enough without also +/// handling them. +/// 2. The long-term goal is to handle higher-kinded concerns +/// in the trait solver, where they belong. This avoids +/// logic duplication and allows future trait solvers +/// to compute better bounds than for example our +/// "must outlive 'static" here. +/// +/// This code is a stop-gap measure in preparation for the future trait solver. +/// +/// Every constraint added by this method is an internal `IllegalUniverse` constraint. +#[instrument(skip(infcx, outlives_constraints))] +pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>( + mut outlives_constraints: OutlivesConstraintSet<'tcx>, + universal_regions: &UniversalRegions<'tcx>, + type_tests: Vec>, + infcx: &BorrowckInferCtxt<'tcx>, + member_constraints: MemberConstraintSet<'tcx, RegionVid>, + errors_buffer: &mut RegionErrors<'tcx>, +) -> LoweredConstraints<'tcx> { + let (definitions, has_placeholders) = region_definitions(universal_regions, infcx); + + if !has_placeholders { + debug!("No placeholder regions found; skipping rewriting logic!"); + let mut annotations = SccAnnotations::init(&definitions); + let sccs = outlives_constraints.compute_sccs( + universal_regions.fr_static, + definitions.len(), + &mut annotations, + ); + return LoweredConstraints { + type_tests, // Pass them through unmodified. + member_constraints: member_constraints.into_mapped( + |r| sccs.scc(r), + |_| true, + |_, _| false, + ), + sccs, + scc_representatives: annotations.scc_to_annotation, + definitions, + outlives_constraints, + }; + } + + debug!("Placeholders present; activating placeholder handling logic!"); + let fr_static = universal_regions.fr_static; + + let mut annotations = SccAnnotations::init(&definitions); + let sccs = outlives_constraints.compute_sccs(fr_static, definitions.len(), &mut annotations); + + let outlives_static = + rewrite_outlives(&sccs, &annotations, fr_static, &mut outlives_constraints, &definitions); + + find_placeholder_mismatch_errors(&definitions, &sccs, &annotations, errors_buffer); + + let (sccs, scc_annotations) = if !outlives_static.is_empty() { + debug!("The following SCCs had :'static constraints added: {:?}", outlives_static); + let mut annotations = SccAnnotations::init(&definitions); + + // We changed the constraint set and so must recompute SCCs. + // Optimisation opportunity: if we can add them incrementally (and that's + // possible because edges to 'static always only merge SCCs into 'static), + // we would potentially save a lot of work here. + ( + outlives_constraints.compute_sccs(fr_static, definitions.len(), &mut annotations), + annotations.scc_to_annotation, + ) + } else { + // If we didn't add any back-edges; no more work needs doing + debug!("No constraints rewritten!"); + (sccs, annotations.scc_to_annotation) + }; + + // Rewrite universe-violating type tests into outlives 'static while we remember + // which universes go where. + let type_tests = type_tests + .into_iter() + .map(|type_test| { + type_test.rewrite_higher_kinded_constraints( + &sccs, + &scc_annotations, + universal_regions, + infcx.tcx, + ) + }) + .collect(); + + let different_universes = |r1, r2| { + scc_annotations[sccs.scc(r1)].min_universe() != scc_annotations[sccs.scc(r2)].min_universe() + }; + + // Rewrite member constraints to exclude choices of regions that would violate + // the respective region's computed (minimum) universe. + let member_constraints = member_constraints.into_mapped( + |r| sccs.scc(r), + |r| scc_annotations[sccs.scc(r)].in_root_universe(), + different_universes, + ); + + // We strip out the extra information and only keep the `Representative`; + // all the information about placeholders and their universes is no longer + // needed. + let scc_representatives = scc_annotations + .into_iter() + .map(|rich_annotation| rich_annotation.into_representative()) + .collect(); + + LoweredConstraints { + type_tests, + sccs, + definitions, + scc_representatives, + member_constraints, + outlives_constraints, + } +} + +fn rewrite_outlives<'tcx>( + sccs: &Sccs, + annotations: &SccAnnotations<'_, '_, RegionTracker>, + fr_static: RegionVid, + outlives_constraints: &mut OutlivesConstraintSet<'tcx>, + definitions: &IndexVec>, +) -> FxHashSet { + // Is this SCC already outliving 'static directly or transitively? + let mut outlives_static = FxHashSet::default(); + + let mut memoised_constraint_graph: Option> = None; + + for scc in sccs.all_sccs() { + let annotation: RegionTracker = annotations.scc_to_annotation[scc]; + // you may be tempted to add 'static to `outlives_static`, but + // we need it to be empty if no constraints were added for a + // later cheap check to see if we did any work. + if scc == sccs.scc(fr_static) { + trace!("Skipping adding 'static: 'static."); + // No use adding 'static: 'static. + continue; + } + + // Figure out if there is a universe violation in this SCC. + // This can happen in two cases: either one of our placeholders + // had its universe lowered from reaching a region with a lower universe, + // (in which case we blame the lower universe's region), or because we reached + // a larger universe (in which case we blame the larger universe's region). + let Some((max_u_rvid, max_u)) = annotation.reaches_too_large_universe() else { + continue; + }; + + let min_u = annotation.min_universe(); + + debug!( + "Universe {max_u:?} is too large for its SCC, represented by {:?}", + annotation.representative + ); + let blame_to = if annotation.representative.rvid() == max_u_rvid { + // We originally had a large enough universe to fit all our reachable + // placeholders, but had it lowered because we also absorbed something + // small-universed. In this case, that's to blame! + let small_universed_rvid = find_region( + outlives_constraints, + memoised_constraint_graph + .get_or_insert_with(|| outlives_constraints.graph(definitions.len())), + definitions, + max_u_rvid, + |r: RegionVid| definitions[r].universe == min_u, + fr_static, + ); + debug!("{small_universed_rvid:?} lowered our universe to {min_u:?}"); + small_universed_rvid + } else { + // The problem is that we, who have a small universe, reach a large one. + max_u_rvid + }; + + outlives_static.insert(scc); + outlives_constraints.push(OutlivesConstraint { + sup: annotation.representative_rvid(), + sub: fr_static, + category: ConstraintCategory::IllegalPlaceholder( + annotation.representative_rvid(), + blame_to, + ), + locations: Locations::All(rustc_span::DUMMY_SP), + span: rustc_span::DUMMY_SP, + variance_info: VarianceDiagInfo::None, + from_closure: false, + }); + } + outlives_static +} + +// FIXME this is at least partially duplicated code to the constraint search in `region_infer`. +/// Find a region matching a predicate in a set of constraints, using BFS. +fn find_region<'tcx>( + constraints: &OutlivesConstraintSet<'tcx>, + graph: &ConstraintGraph, + definitions: &IndexVec>, + start_region: RegionVid, + target_test: impl Fn(RegionVid) -> bool, + fr_static: RegionVid, +) -> RegionVid { + #[derive(Clone, PartialEq, Eq, Debug)] + enum Trace { + StartRegion, + NotVisited, + Visited, + } + + let graph = RegionGraph::new(constraints, graph, fr_static); + + let mut context = IndexVec::from_elem(Trace::NotVisited, definitions); + context[start_region] = Trace::StartRegion; + + let mut deque = VecDeque::new(); + deque.push_back(start_region); + + while let Some(r) = deque.pop_front() { + if target_test(r) { + return r; + } + + for sub_region in graph.outgoing_regions(r) { + if let Trace::NotVisited = context[sub_region] { + context[sub_region] = Trace::Visited; + deque.push_back(sub_region); + } + } + } + // since this function is used exclusively in this module, we know + // we are only searching for regions we found in the region graph, + // so if we don't find what we are looking for there's a bug somwehere. + bug!("Should have found something!"); +} + +struct TypeTestRewriter<'c, 'tcx> { + lower_scc: ConstraintSccIndex, + sccs: &'c Sccs, + scc_annotations: &'c IndexVec, + universal_regions: &'c UniversalRegions<'tcx>, + tcx: TyCtxt<'tcx>, + generic_kind: GenericKind<'tcx>, +} + +impl<'c, 'tcx> TypeTestRewriter<'c, 'tcx> { + fn annotation(&self, rvid: RegionVid) -> RegionTracker { + self.scc_annotations[self.sccs.scc(rvid)] + } + + /// Determine if a region is compatible with the lower bound's + /// universe. + fn universe_compatible_with_bound(&self, r: Region<'tcx>) -> bool { + let rvid = self.universal_regions.to_region_vid(r); + rvid == self.universal_regions.fr_static + || self.annotation(rvid).universe_compatible_with(self.scc_annotations[self.lower_scc]) + } + + #[instrument(skip(self), ret)] + fn rewrite(&self, bound: &VerifyBound<'tcx>) -> Option> { + let lower = self.scc_annotations[self.lower_scc]; + match bound { + // You may think that an equality bound would imply universe + // equality, and it does -- except that we do not track placeholders, + // and so in the event that you have two empty regions, one of which is + // in an unnameable universe, they would compare equal since they + // are both empty. This bit ensures that whatever comes out of the + // bound also matches the placeholder reachability of the lower bound. + VerifyBound::IfEq(verify_if_eq_b) => { + // this bit picks out the worst possible candidate that can end up for the match + // in terms of its universe. + let mut m = MatchUniverses::new( + self.tcx, + self.sccs, + self.scc_annotations, + self.universal_regions, + ); + let verify_if_eq = verify_if_eq_b.skip_binder(); + let what_error = m.relate(verify_if_eq.ty, self.generic_kind.to_ty(self.tcx)); + if let Err(e) = what_error { + debug!( + "Type test {verify_if_eq_b:?} {generic_kind:?} failed to match with {e:?}", + generic_kind = self.generic_kind + ); + return Some(VerifyBound::never_satisfied()); + } + + let r = if let ty::RegionKind::ReBound(depth, _) = verify_if_eq.bound.kind() { + assert!(depth == ty::INNERMOST); + m.max_universe_region.map_or(self.tcx.lifetimes.re_static, |pair| pair.1) + } else { + verify_if_eq.bound + }; + + if self.universe_compatible_with_bound(r) { + None + } else { + Some(VerifyBound::never_satisfied()) + } + } + // Rewrite an outlives bound to an outlives-static bound upon referencing + // an unnameable universe (from a placeholder). + VerifyBound::OutlivedBy(region) => { + if self.universe_compatible_with_bound(*region) { + None + } else { + Some(VerifyBound::OutlivesStatic(*region)) + } + } + // Nothing in here to violate a universe, but since we can't detect + // bounds being violated by placeholders when we don't track placeholders, + // we ensure that we don't reach any. + VerifyBound::IsEmpty => { + if matches!(lower.reachable_placeholders, PlaceholderReachability::NoPlaceholders) { + None + } else { + debug!("Empty bound reaches placeholders: {:?}", lower.reachable_placeholders); + Some(VerifyBound::never_satisfied()) + } + } + VerifyBound::AnyBound(verify_bounds) => { + self.rewrite_bounds(verify_bounds).map(VerifyBound::AnyBound) + } + + VerifyBound::AllBounds(verify_bounds) => { + self.rewrite_bounds(verify_bounds).map(VerifyBound::AllBounds) + } + VerifyBound::OutlivesStatic(_) => { + bug!("Unexpected OutlivesStatic bound; they should not have been introduced yet!") + } + } + } + + fn rewrite_bounds( + &self, + verify_bounds: &Vec>, + ) -> Option>> { + let mut bounds = Vec::with_capacity(verify_bounds.len()); + let mut rewrote_any = false; + for bound in verify_bounds { + let bound = if let Some(rewritten) = self.rewrite(&bound) { + rewrote_any = true; + rewritten + } else { + bound.clone() + }; + bounds.push(bound); + } + + if rewrote_any { Some(bounds) } else { None } + } +} + +impl<'t> TypeTest<'t> { + #[instrument(skip(sccs, tcx, universal_regions, scc_annotations), ret)] + fn rewrite_higher_kinded_constraints( + self, + sccs: &Sccs, + scc_annotations: &IndexVec, + universal_regions: &UniversalRegions<'t>, + tcx: TyCtxt<'t>, + ) -> Self { + let rewriter = TypeTestRewriter { + generic_kind: self.generic_kind, + sccs, + scc_annotations, + universal_regions, + tcx, + lower_scc: sccs.scc(self.lower_bound), + }; + + if let Some(rewritten_bound) = rewriter.rewrite(&self.verify_bound) { + TypeTest { + verify_bound: rewritten_bound, + generic_kind: self.generic_kind, + lower_bound: self.lower_bound, + source: TypeTestOrigin::Rewritten(Box::new(self)), + } + } else { + self + } + } +} + +impl<'tcx, 'v> TypeRelation> for MatchUniverses<'tcx, 'v> { + fn cx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + #[instrument(level = "trace", skip(self))] + fn relate_with_variance>>( + &mut self, + variance: ty::Variance, + _: ty::VarianceDiagInfo>, + a: T, + b: T, + ) -> RelateResult<'tcx, T> { + // Opaque types args have lifetime parameters. + // We must not check them to be equal, as we never insert anything to make them so. + if variance != ty::Bivariant { self.relate(a, b) } else { Ok(a) } + } + + #[instrument(skip(self), level = "trace")] + fn regions( + &mut self, + pattern: ty::Region<'tcx>, + value: ty::Region<'tcx>, + ) -> RelateResult<'tcx, ty::Region<'tcx>> { + // `pattern` is from inside `VerifyBound::IfEq`, and `value` from `generic_kind` (what we're looking for). + if pattern == value { + self.update_max_universe(pattern); + } else { + assert!( + pattern.is_bound() || self.universe_of(pattern).is_root(), + "{pattern:?} neither bound nor in root universe. Universe is: {:?}, kind: {:?}", + self.universe_of(pattern), + pattern.kind() + ); + } + + if let Some((_, max_universed_region)) = self.max_universe_region.as_ref() { + Ok(*max_universed_region) + } else { + Ok(pattern) + } + } + + #[instrument(skip(self), level = "trace")] + fn tys(&mut self, pattern: Ty<'tcx>, value: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { + relate::structurally_relate_tys(self, pattern, value) + } + + #[instrument(skip(self), level = "trace")] + fn consts( + &mut self, + pattern: ty::Const<'tcx>, + value: ty::Const<'tcx>, + ) -> RelateResult<'tcx, ty::Const<'tcx>> { + relate::structurally_relate_consts(self, pattern, value) + } + + #[instrument(skip(self), level = "trace")] + fn binders( + &mut self, + pattern: ty::Binder<'tcx, T>, + value: ty::Binder<'tcx, T>, + ) -> RelateResult<'tcx, ty::Binder<'tcx, T>> + where + T: Relate>, + { + self.pattern_depth.shift_in(1); + let result = Ok(pattern.rebind(self.relate(pattern.skip_binder(), value.skip_binder())?)); + self.pattern_depth.shift_out(1); + result + } +} + +/// A `TypeRelation` visitor that computes the largest universe. +struct MatchUniverses<'tcx, 'v> { + tcx: TyCtxt<'tcx>, + pattern_depth: ty::DebruijnIndex, + max_universe_region: Option<(UniverseIndex, ty::Region<'tcx>)>, + sccs: &'v Sccs, + scc_annotations: &'v IndexVec, + universal_regions: &'v UniversalRegions<'tcx>, +} + +impl<'tcx, 'v> MatchUniverses<'tcx, 'v> { + fn new( + tcx: TyCtxt<'tcx>, + sccs: &'v Sccs, + scc_annotations: &'v IndexVec, + universal_regions: &'v UniversalRegions<'tcx>, + ) -> MatchUniverses<'tcx, 'v> { + MatchUniverses { + tcx, + pattern_depth: ty::INNERMOST, + max_universe_region: None, + scc_annotations, + sccs, + universal_regions, + } + } + + fn universe_of(&self, r: ty::Region<'tcx>) -> UniverseIndex { + self.scc_annotations[self.sccs.scc(self.universal_regions.to_region_vid(r))].min_universe() + } + + #[instrument(skip(self), level = "trace")] + fn update_max_universe(&mut self, r: ty::Region<'tcx>) { + let r_universe = self.universe_of(r); + + let Some((current_max_u, current_max_r)) = self.max_universe_region else { + self.max_universe_region = Some((r_universe, r)); + return; + }; + self.max_universe_region = if r_universe > current_max_u { + Some((r_universe, r)) + } else { + Some((current_max_u, current_max_r)) + } + } +} diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 51d37353520b8..20d28a7501349 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -75,6 +75,7 @@ mod constraints; mod dataflow; mod def_use; mod diagnostics; +mod eliminate_placeholders; mod member_constraints; mod nll; mod path_utils; @@ -2615,6 +2616,33 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { tcx.emit_node_span_lint(UNUSED_MUT, lint_root, span, VarNeedNotMut { span: mut_span }) } } + + /// Report that longer_fr: shorter_fr, which doesn't hold, + /// where longer_fr is a placeholder from `placeholder`. + fn report_erroneous_rvid_reaches_placeholder( + &mut self, + longer_fr: RegionVid, + placeholder: ty::Placeholder, + error_vid: RegionVid, + ) { + // Find the code to blame for the fact that `longer_fr` outlives `error_fr`. + let (_, cause) = self.regioncx.find_outlives_blame_span( + longer_fr, + NllRegionVariableOrigin::Placeholder(placeholder), + error_vid, + ); + + // FIXME these methods should have better names, and also probably not be this generic. + // FIXME note that we *throw away* the error element here! We probably want to + // thread it through the computation further down and use it, but there currently isn't + // anything there to receive it. + self.regioncx.universe_info(placeholder.universe).report_erroneous_element( + self, + placeholder, + cause, + None, + ); + } } /// The degree of overlap between 2 places for borrow-checking. diff --git a/compiler/rustc_borrowck/src/member_constraints.rs b/compiler/rustc_borrowck/src/member_constraints.rs index bdd0f6fe11e0f..08aeedd048bf5 100644 --- a/compiler/rustc_borrowck/src/member_constraints.rs +++ b/compiler/rustc_borrowck/src/member_constraints.rs @@ -1,11 +1,11 @@ use std::hash::Hash; use std::ops::Index; -use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::ty::{self, Ty}; use rustc_span::Span; -use tracing::instrument; +use tracing::{debug, instrument}; /// Compactly stores a set of `R0 member of [R1...Rn]` constraints, /// indexed by the region `R0`. @@ -45,6 +45,12 @@ pub(crate) struct MemberConstraint<'tcx> { /// The region `R0`. pub(crate) member_region_vid: ty::RegionVid, + /// Set to `true` if this constraint should be skipped by `apply()`, + /// but must be checked. + pub(crate) should_be_applied: bool, + + bad_choice_regions: FxIndexSet, + /// Index of `R1` in `choice_regions` vector from `MemberConstraintSet`. start_index: usize, @@ -94,6 +100,8 @@ impl<'tcx> MemberConstraintSet<'tcx, ty::RegionVid> { key, start_index, end_index, + should_be_applied: true, // All constraints should be applied until otherwise noted. + bad_choice_regions: FxIndexSet::default(), }); self.first_constraints.insert(member_region_vid, constraint_index); } @@ -111,6 +119,8 @@ where pub(crate) fn into_mapped( self, mut map_fn: impl FnMut(R1) -> R2, + should_apply: impl Fn(ty::RegionVid) -> bool, + bad_choice: impl Fn(ty::RegionVid, ty::RegionVid) -> bool, ) -> MemberConstraintSet<'tcx, R2> where R2: Copy + Hash + Eq, @@ -138,6 +148,22 @@ where first_constraints2.insert(r2, start1); } + for constraint in constraints.iter_mut() { + let MemberConstraint { + member_region_vid, + start_index, + end_index, + bad_choice_regions, + .. + } = constraint; + constraint.should_be_applied = should_apply(*member_region_vid); + bad_choice_regions.extend( + choice_regions[*start_index..*end_index] + .iter() + .filter(|&&choice_r| bad_choice(*member_region_vid, choice_r)), + ); + } + MemberConstraintSet { first_constraints: first_constraints2, constraints, choice_regions } } } @@ -174,9 +200,32 @@ where /// ```text /// R0 member of [R1..Rn] /// ``` - pub(crate) fn choice_regions(&self, pci: NllMemberConstraintIndex) -> &[ty::RegionVid] { - let MemberConstraint { start_index, end_index, .. } = &self.constraints[pci]; - &self.choice_regions[*start_index..*end_index] + pub(crate) fn choice_regions( + &self, + pci: NllMemberConstraintIndex, + ) -> impl Iterator + use<'_, R> { + let MemberConstraint { start_index, end_index, bad_choice_regions, .. } = + &self.constraints[pci]; + self.choice_regions[*start_index..*end_index] + .iter() + .filter_map(|r| if bad_choice_regions.contains(r) { None } else { Some(*r) }) + } + + /// Apply member constraints (unless they are flagged for non-application) + pub(crate) fn apply( + &self, + idx: R, + mut do_apply: impl FnMut(NllMemberConstraintIndex, &[ty::RegionVid]), + ) { + for pci in self.indices(idx) { + let MemberConstraint { start_index, end_index, should_be_applied, .. } = + &self.constraints[pci]; + if *should_be_applied { + do_apply(pci, &self.choice_regions[*start_index..*end_index]); + } else { + debug!("Skipping member constraint with index {pci:?}; marked for ignoring"); + } + } } } diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index fe899bb054fa9..a23929a585559 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -22,12 +22,13 @@ use tracing::{debug, instrument}; use crate::borrow_set::BorrowSet; use crate::consumers::ConsumerOptions; use crate::diagnostics::RegionErrors; +use crate::eliminate_placeholders::rewrite_higher_kinded_outlives_as_constraints; use crate::polonius::PoloniusDiagnosticsContext; use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, }; use crate::region_infer::RegionInferenceContext; -use crate::type_check::{self, MirTypeckResults}; +use crate::type_check::{self, MirTypeckRegionConstraints, MirTypeckResults}; use crate::universal_regions::UniversalRegions; use crate::{ BorrowCheckRootCtxt, BorrowckInferCtxt, ClosureOutlivesSubject, ClosureRegionRequirements, @@ -129,8 +130,47 @@ pub(crate) fn compute_regions<'a, 'tcx>( &constraints, ); - let mut regioncx = - RegionInferenceContext::new(infcx, constraints, universal_region_relations, location_map); + let MirTypeckRegionConstraints { + liveness_constraints, + mut outlives_constraints, + mut member_constraints, + universe_causes, + type_tests, + .. + } = constraints; + + let universal_regions = &universal_region_relations.universal_regions; + + if let Some(guar) = universal_regions.tainted_by_errors() { + debug!("Universal regions tainted by errors; removing constraints!"); + // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all + // outlives bounds that we may end up checking. + outlives_constraints = Default::default(); + member_constraints = Default::default(); + + // Also taint the entire scope. + infcx.set_tainted_by_errors(guar); + } + + let mut placeholder_errors = RegionErrors::new(infcx.tcx); + + let lowered_constraints = rewrite_higher_kinded_outlives_as_constraints( + outlives_constraints, + &universal_regions, + type_tests, + infcx, + member_constraints, + &mut placeholder_errors, + ); + + let mut regioncx = RegionInferenceContext::new( + infcx, + universal_region_relations, + location_map, + universe_causes, + liveness_constraints, + lowered_constraints, + ); // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints // and use them to compute loan liveness. @@ -160,9 +200,17 @@ pub(crate) fn compute_regions<'a, 'tcx>( }); // Solve the region constraints. - let (closure_region_requirements, nll_errors) = + let (closure_region_requirements, region_inference_errors) = regioncx.solve(infcx, body, polonius_output.clone()); + let nll_errors = if region_inference_errors.has_errors().is_some() { + debug!("Errors already reported, skipping these: {placeholder_errors:?}"); + region_inference_errors + } else { + // Only flag the higher-kinded bounds errors if there are no borrowck errors. + placeholder_errors + }; + if let Some(guar) = nll_errors.has_errors() { // Suppress unhelpful extra errors in `infer_opaque_types`. infcx.set_tainted_by_errors(guar); diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index ef3d6309c19c2..7d46e7d835aac 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -43,10 +43,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { for region in self.regions() { writeln!( out, - "| {r:rw$?} | {ui:4?} | {v}", + "| {r:rw$?} | {v}", r = region, rw = REGION_WIDTH, - ui = self.region_universe(region), v = self.region_value_str(region), )?; } diff --git a/compiler/rustc_borrowck/src/region_infer/graphviz.rs b/compiler/rustc_borrowck/src/region_infer/graphviz.rs index 1936752b63c6e..d5f4dd9c5f50e 100644 --- a/compiler/rustc_borrowck/src/region_infer/graphviz.rs +++ b/compiler/rustc_borrowck/src/region_infer/graphviz.rs @@ -12,6 +12,9 @@ use rustc_middle::ty::UniverseIndex; use super::*; fn render_outlives_constraint(constraint: &OutlivesConstraint<'_>) -> String { + if let ConstraintCategory::IllegalPlaceholder(from, to) = constraint.category { + return format!("b/c {from:?}: {to:?}"); + } match constraint.locations { Locations::All(_) => "All(...)".to_string(), Locations::Single(loc) => format!("{loc:?}"), diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index c256051c122fa..a4606164b7d3f 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -6,10 +6,12 @@ use rustc_data_structures::frozen::Frozen; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::graph::scc::{self, Sccs}; use rustc_errors::Diag; -use rustc_hir::def_id::CRATE_DEF_ID; +use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; use rustc_index::IndexVec; use rustc_infer::infer::outlives::test_type_match; -use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound, VerifyIfEq}; +use rustc_infer::infer::region_constraints::{ + GenericKind, RegionVariableInfo, VerifyBound, VerifyIfEq, +}; use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin}; use rustc_middle::bug; use rustc_middle::mir::{ @@ -17,7 +19,9 @@ use rustc_middle::mir::{ TerminatorKind, }; use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; -use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeFoldable, UniverseIndex, fold_regions}; +use rustc_middle::ty::{ + self, PlaceholderRegion, RegionVid, Ty, TyCtxt, TypeFoldable, fold_regions, +}; use rustc_mir_dataflow::points::DenseLocationMap; use rustc_span::hygiene::DesugaringKind; use rustc_span::{DUMMY_SP, Span}; @@ -27,13 +31,14 @@ use crate::constraints::graph::{self, NormalConstraintGraph, RegionGraph}; use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet}; use crate::dataflow::BorrowIndex; use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo}; +use crate::eliminate_placeholders::LoweredConstraints; use crate::member_constraints::{MemberConstraintSet, NllMemberConstraintIndex}; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; use crate::region_infer::reverse_sccs::ReverseSccGraph; use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues, ToElementIndex}; +use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; -use crate::type_check::{Locations, MirTypeckRegionConstraints}; use crate::universal_regions::UniversalRegions; use crate::{ BorrowckInferCtxt, ClosureOutlivesRequirement, ClosureOutlivesSubject, @@ -47,96 +52,46 @@ mod reverse_sccs; pub(crate) mod values; -pub(crate) type ConstraintSccs = Sccs; - -/// An annotation for region graph SCCs that tracks -/// the values of its elements. -#[derive(Copy, Debug, Clone)] -pub struct RegionTracker { - /// The largest universe of a placeholder reached from this SCC. - /// This includes placeholders within this SCC. - max_placeholder_universe_reached: UniverseIndex, - - /// The smallest universe index reachable form the nodes of this SCC. - min_reachable_universe: UniverseIndex, - - /// The representative Region Variable Id for this SCC. We prefer - /// placeholders over existentially quantified variables, otherwise - /// it's the one with the smallest Region Variable ID. - pub(crate) representative: RegionVid, - - /// Is the current representative a placeholder? - representative_is_placeholder: bool, - - /// Is the current representative existentially quantified? - representative_is_existential: bool, +/// The representative region variable for an SCC, tagged by its origin. +/// We prefer placeholders over existentially quantified variables, otherwise +/// it's the one with the smallest Region Variable ID. +#[derive(Copy, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub(crate) enum Representative { + FreeRegion(RegionVid), + Placeholder(RegionVid), + Existential(RegionVid), } -impl scc::Annotation for RegionTracker { - fn merge_scc(mut self, mut other: Self) -> Self { - // Prefer any placeholder over any existential - if other.representative_is_placeholder && self.representative_is_existential { - other.merge_min_max_seen(&self); - return other; +impl Representative { + pub(crate) fn rvid(&self) -> RegionVid { + match self { + Representative::FreeRegion(region_vid) => *region_vid, + Representative::Placeholder(region_vid) => *region_vid, + Representative::Existential(region_vid) => *region_vid, } - - if self.representative_is_placeholder && other.representative_is_existential - || (self.representative <= other.representative) - { - self.merge_min_max_seen(&other); - return self; - } - other.merge_min_max_seen(&self); - other - } - - fn merge_reached(mut self, other: Self) -> Self { - // No update to in-component values, only add seen values. - self.merge_min_max_seen(&other); - self } -} - -impl RegionTracker { - pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self { - let (representative_is_placeholder, representative_is_existential) = match definition.origin - { - NllRegionVariableOrigin::FreeRegion => (false, false), - NllRegionVariableOrigin::Placeholder(_) => (true, false), - NllRegionVariableOrigin::Existential { .. } => (false, true), - }; - let placeholder_universe = - if representative_is_placeholder { definition.universe } else { UniverseIndex::ROOT }; - - Self { - max_placeholder_universe_reached: placeholder_universe, - min_reachable_universe: definition.universe, - representative: rvid, - representative_is_placeholder, - representative_is_existential, + pub(crate) fn new( + r: RegionVid, + definitions: &IndexVec>, + ) -> Self { + match definitions[r].origin { + NllRegionVariableOrigin::FreeRegion => Representative::FreeRegion(r), + NllRegionVariableOrigin::Placeholder(_) => Representative::Placeholder(r), + NllRegionVariableOrigin::Existential { .. } => Representative::Existential(r), } } +} - /// The smallest-indexed universe reachable from and/or in this SCC. - fn min_universe(self) -> UniverseIndex { - self.min_reachable_universe - } - - fn merge_min_max_seen(&mut self, other: &Self) { - self.max_placeholder_universe_reached = std::cmp::max( - self.max_placeholder_universe_reached, - other.max_placeholder_universe_reached, - ); - - self.min_reachable_universe = - std::cmp::min(self.min_reachable_universe, other.min_reachable_universe); +impl scc::Annotation for Representative { + fn merge_scc(self, other: Self) -> Self { + // Just pick the smallest one. Note that we order by tag first! + std::cmp::min(self, other) } - /// Returns `true` if during the annotated SCC reaches a placeholder - /// with a universe larger than the smallest reachable one, `false` otherwise. - pub(crate) fn has_incompatible_universes(&self) -> bool { - self.min_universe().cannot_name(self.max_placeholder_universe_reached) + // For reachability, we do nothing since the representative doesn't change. + fn merge_reached(self, _other: Self) -> Self { + self } } @@ -164,7 +119,9 @@ pub struct RegionInferenceContext<'tcx> { /// The SCC computed from `constraints` and the constraint /// graph. We have an edge from SCC A to SCC B if `A: B`. Used to /// compute the values of each region. - constraint_sccs: ConstraintSccs, + constraint_sccs: Sccs, + + scc_representatives: IndexVec, /// Reverse of the SCC constraint graph -- i.e., an edge `A -> B` exists if /// `B: A`. This is used to compute the universal regions that are required @@ -252,6 +209,29 @@ pub(crate) enum Cause { DropVar(Local, Location), } +/// Where a type test came from. +#[derive(Clone, Debug)] +pub(crate) enum TypeTestOrigin<'tcx> { + /// This type test was generated from user-written code at this span. + Code(Span), + /// This type test was rewritten from this original type test. + Rewritten(Box>), +} + +impl<'tcx> TypeTestOrigin<'tcx> { + fn span(&self) -> Span { + match self { + TypeTestOrigin::Code(span) => *span, + TypeTestOrigin::Rewritten(type_test) => match type_test.source { + TypeTestOrigin::Code(span) => span, + TypeTestOrigin::Rewritten(_) => { + bug!("Type tests should only have been rewritten once!") + } + }, + } + } +} + /// A "type test" corresponds to an outlives constraint between a type /// and a lifetime, like `T: 'x` or `::Bar: 'x`. They are /// translated from the `Verify` region constraints in the ordinary @@ -292,8 +272,8 @@ pub(crate) struct TypeTest<'tcx> { /// The region `'x` that the type must outlive. pub lower_bound: RegionVid, - /// The span to blame. - pub span: Span, + /// The reason this type test exists. + pub source: TypeTestOrigin<'tcx>, /// A test which, if met by the region `'x`, proves that this type /// constraint is satisfied. @@ -319,7 +299,10 @@ enum Trace<'a, 'tcx> { } #[instrument(skip(infcx, sccs), level = "debug")] -fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { +fn sccs_info<'tcx>( + infcx: &BorrowckInferCtxt<'tcx>, + sccs: &scc::Sccs, +) { use crate::renumber::RegionCtxt; let var_to_origin = infcx.reg_var_to_origin.borrow(); @@ -384,26 +367,6 @@ fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) { debug!("SCC edges {:#?}", scc_node_to_edges); } -fn create_definitions<'tcx>( - infcx: &BorrowckInferCtxt<'tcx>, - universal_regions: &UniversalRegions<'tcx>, -) -> Frozen>> { - // Create a RegionDefinition for each inference variable. - let mut definitions: IndexVec<_, _> = infcx - .get_region_var_infos() - .iter() - .map(|info| RegionDefinition::new(info.universe, info.origin)) - .collect(); - - // Add the external name for all universal regions. - for (external_name, variable) in universal_regions.named_universal_regions_iter() { - debug!("region {variable:?} has external name {external_name:?}"); - definitions[variable].external_name = Some(external_name); - } - - Frozen::freeze(definitions) -} - impl<'tcx> RegionInferenceContext<'tcx> { /// Creates a new region inference context with a total of /// `num_region_variables` valid inference variables; the first N @@ -414,155 +377,71 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// of constraints produced by the MIR type check. pub(crate) fn new( infcx: &BorrowckInferCtxt<'tcx>, - constraints: MirTypeckRegionConstraints<'tcx>, universal_region_relations: Frozen>, location_map: Rc, + universe_causes: FxIndexMap>, + mut liveness_constraints: LivenessValues, + lowered_constraints: LoweredConstraints<'tcx>, ) -> Self { let universal_regions = &universal_region_relations.universal_regions; - let MirTypeckRegionConstraints { - placeholder_indices, - placeholder_index_to_region: _, - liveness_constraints, - mut outlives_constraints, - mut member_constraints, - universe_causes, + + let LoweredConstraints { type_tests, - } = constraints; + sccs: constraint_sccs, + definitions, + scc_representatives, + member_constraints, + outlives_constraints, + } = lowered_constraints; debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); debug!("outlives constraints: {:#?}", outlives_constraints); - debug!("placeholder_indices: {:#?}", placeholder_indices); debug!("type tests: {:#?}", type_tests); - if let Some(guar) = universal_region_relations.universal_regions.tainted_by_errors() { - // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all - // outlives bounds that we may end up checking. - outlives_constraints = Default::default(); - member_constraints = Default::default(); - - // Also taint the entire scope. - infcx.set_tainted_by_errors(guar); - } - - let definitions = create_definitions(infcx, &universal_regions); - - let constraint_sccs = - outlives_constraints.add_outlives_static(&universal_regions, &definitions); let constraints = Frozen::freeze(outlives_constraints); let constraint_graph = Frozen::freeze(constraints.graph(definitions.len())); if cfg!(debug_assertions) { - sccs_info(infcx, &constraint_sccs); + sccs_info(infcx, &constraint_sccs) } - let mut scc_values = - RegionValues::new(location_map, universal_regions.len(), placeholder_indices); + let mut scc_values = RegionValues::new(location_map, universal_regions.len()); for region in liveness_constraints.regions() { let scc = constraint_sccs.scc(region); scc_values.merge_liveness(scc, region, &liveness_constraints); } - let member_constraints = - Rc::new(member_constraints.into_mapped(|r| constraint_sccs.scc(r))); + // Initialise free, universally quantified regions to be live at all points. + for variable in definitions.indices() { + if let NllRegionVariableOrigin::FreeRegion = definitions[variable].origin { + // For each free, universally quantified region X: + + let scc = constraint_sccs.scc(variable); + + // Add all nodes in the CFG to liveness constraints + liveness_constraints.add_all_points(variable); + scc_values.add_all_points(scc); + + // Add `end(X)` into the set for X. + scc_values.add_element(scc, variable); + } + } - let mut result = Self { + Self { definitions, liveness_constraints, constraints, constraint_graph, constraint_sccs, rev_scc_graph: None, - member_constraints, + member_constraints: Rc::new(member_constraints), member_constraints_applied: Vec::new(), universe_causes, scc_values, type_tests, universal_region_relations, - }; - - result.init_free_and_bound_regions(); - - result - } - - /// Initializes the region variables for each universally - /// quantified region (lifetime parameter). The first N variables - /// always correspond to the regions appearing in the function - /// signature (both named and anonymous) and where-clauses. This - /// function iterates over those regions and initializes them with - /// minimum values. - /// - /// For example: - /// ``` - /// fn foo<'a, 'b>( /* ... */ ) where 'a: 'b { /* ... */ } - /// ``` - /// would initialize two variables like so: - /// ```ignore (illustrative) - /// R0 = { CFG, R0 } // 'a - /// R1 = { CFG, R0, R1 } // 'b - /// ``` - /// Here, R0 represents `'a`, and it contains (a) the entire CFG - /// and (b) any universally quantified regions that it outlives, - /// which in this case is just itself. R1 (`'b`) in contrast also - /// outlives `'a` and hence contains R0 and R1. - /// - /// This bit of logic also handles invalid universe relations - /// for higher-kinded types. - /// - /// We Walk each SCC `A` and `B` such that `A: B` - /// and ensure that universe(A) can see universe(B). - /// - /// This serves to enforce the 'empty/placeholder' hierarchy - /// (described in more detail on `RegionKind`): - /// - /// ```ignore (illustrative) - /// static -----+ - /// | | - /// empty(U0) placeholder(U1) - /// | / - /// empty(U1) - /// ``` - /// - /// In particular, imagine we have variables R0 in U0 and R1 - /// created in U1, and constraints like this; - /// - /// ```ignore (illustrative) - /// R1: !1 // R1 outlives the placeholder in U1 - /// R1: R0 // R1 outlives R0 - /// ``` - /// - /// Here, we wish for R1 to be `'static`, because it - /// cannot outlive `placeholder(U1)` and `empty(U0)` any other way. - /// - /// Thanks to this loop, what happens is that the `R1: R0` - /// constraint has lowered the universe of `R1` to `U0`, which in turn - /// means that the `R1: !1` constraint here will cause - /// `R1` to become `'static`. - fn init_free_and_bound_regions(&mut self) { - for variable in self.definitions.indices() { - let scc = self.constraint_sccs.scc(variable); - - match self.definitions[variable].origin { - NllRegionVariableOrigin::FreeRegion => { - // For each free, universally quantified region X: - - // Add all nodes in the CFG to liveness constraints - self.liveness_constraints.add_all_points(variable); - self.scc_values.add_all_points(scc); - - // Add `end(X)` into the set for X. - self.scc_values.add_element(scc, variable); - } - - NllRegionVariableOrigin::Placeholder(placeholder) => { - self.scc_values.add_element(scc, placeholder); - } - - NllRegionVariableOrigin::Existential { .. } => { - // For existential, regions, nothing to do. - } - } + scc_representatives, } } @@ -619,19 +498,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.scc_values.region_value_str(scc) } - pub(crate) fn placeholders_contained_in( - &self, - r: RegionVid, - ) -> impl Iterator { - let scc = self.constraint_sccs.scc(r); - self.scc_values.placeholders_contained_in(scc) - } - - /// Returns access to the value of `r` for debugging purposes. - pub(crate) fn region_universe(&self, r: RegionVid) -> ty::UniverseIndex { - self.scc_universe(self.constraint_sccs.scc(r)) - } - /// Once region solving has completed, this function will return the member constraints that /// were applied to the value of a given SCC `scc`. See `AppliedMemberConstraint`. pub(crate) fn applied_member_constraints( @@ -655,19 +521,12 @@ impl<'tcx> RegionInferenceContext<'tcx> { body: &Body<'tcx>, polonius_output: Option>, ) -> (Option>, RegionErrors<'tcx>) { - let mir_def_id = body.source.def_id(); self.propagate_constraints(); let mut errors_buffer = RegionErrors::new(infcx.tcx); - // If this is a closure, we can propagate unsatisfied - // `outlives_requirements` to our creator, so create a vector - // to store those. Otherwise, we'll pass in `None` to the - // functions below, which will trigger them to report errors - // eagerly. - let mut outlives_requirements = infcx.tcx.is_typeck_child(mir_def_id).then(Vec::new); - - self.check_type_tests(infcx, outlives_requirements.as_mut(), &mut errors_buffer); + let mut outlives_requirements = + self.check_type_tests(infcx, body.source.def_id(), &mut errors_buffer); debug!(?errors_buffer); debug!(?outlives_requirements); @@ -687,14 +546,12 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.check_universal_regions(outlives_requirements.as_mut(), &mut errors_buffer); } - debug!(?errors_buffer); - if errors_buffer.is_empty() { self.check_member_constraints(infcx, &mut errors_buffer); + } else { + debug!("Previous errors, skipping member constraint checks: {errors_buffer:?}"); } - debug!(?errors_buffer); - let outlives_requirements = outlives_requirements.unwrap_or_default(); if outlives_requirements.is_empty() { @@ -749,10 +606,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { } // Now take member constraints into account. - let member_constraints = Rc::clone(&self.member_constraints); - for m_c_i in member_constraints.indices(scc_a) { - self.apply_member_constraint(scc_a, m_c_i, member_constraints.choice_regions(m_c_i)); - } + Rc::clone(&self.member_constraints) + .apply(scc_a, |m_c_i, regions| self.apply_member_constraint(scc_a, m_c_i, regions)); debug!(value = ?self.scc_values.region_value_str(scc_a)); } @@ -796,12 +651,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { *c_r = self.scc_representative(scc); } - // If the member region lives in a higher universe, we currently choose - // the most conservative option by leaving it unchanged. - if !self.constraint_sccs().annotation(scc).min_universe().is_root() { - return; - } - // The existing value for `scc` is a lower-bound. This will // consist of some set `{P} + {LB}` of points `{P}` and // lower-bound free regions `{LB}`. As each choice region `O` @@ -870,24 +719,38 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } - /// Returns `true` if all the elements in the value of `scc_b` are nameable - /// in `scc_a`. Used during constraint propagation, and only once - /// the value of `scc_b` has been computed. - fn universe_compatible(&self, scc_b: ConstraintSccIndex, scc_a: ConstraintSccIndex) -> bool { - let a_annotation = self.constraint_sccs().annotation(scc_a); - let b_annotation = self.constraint_sccs().annotation(scc_b); - let a_universe = a_annotation.min_universe(); - - // If scc_b's declared universe is a subset of - // scc_a's declared universe (typically, both are ROOT), then - // it cannot contain any problematic universe elements. - if a_universe.can_name(b_annotation.min_universe()) { - return true; - } + #[instrument(level = "debug", skip(self, infcx, propagated_outlives_requirements), ret)] + fn handle_type_test( + &self, + infcx: &InferCtxt<'tcx>, + tt: &TypeTest<'tcx>, + propagated_outlives_requirements: Option<&mut Vec>>, + ) -> Option> { + let tentative_error = + self.eval_verify_bound(infcx, &tt, &tt.verify_bound).map(|failing_bound| { + let rewritten_to_fail = if let VerifyBound::AnyBound(v) = failing_bound { + v.is_empty() && matches!(tt.source, TypeTestOrigin::Rewritten(..)) + } else { + false + }; + let failed_due_to_placeholders = + matches!(failing_bound, VerifyBound::OutlivesStatic(_)) || rewritten_to_fail; + + RegionErrorKind::TypeTestError { + lower_bound: tt.lower_bound, + span: tt.source.span(), + generic_kind: tt.generic_kind, + failed_due_to_placeholders, + } + }); - // Otherwise, there can be no placeholder in `b` with a too high - // universe index to name from `a`. - a_universe.can_name(b_annotation.max_placeholder_universe_reached) + tentative_error.and_then(|error| { + if let Some(propagated_outlives_reqs) = propagated_outlives_requirements { + self.try_promote_type_test(infcx, &tt, propagated_outlives_reqs, error) + } else { + Some(error) + } + }) } /// Once regions have been propagated, this method is used to see @@ -897,54 +760,45 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn check_type_tests( &self, infcx: &InferCtxt<'tcx>, - mut propagated_outlives_requirements: Option<&mut Vec>>, + mir_def_id: DefId, errors_buffer: &mut RegionErrors<'tcx>, - ) { - let tcx = infcx.tcx; + ) -> Option>> { + // If this is a closure, we can propagate unsatisfied + // `outlives_requirements` to our creator, so create a vector + // to store those. Otherwise, we'll pass in `None` to the + // functions below, which will trigger them to report errors + // eagerly. + let mut propagated_outlives_requirements = + infcx.tcx.is_typeck_child(mir_def_id).then(Vec::new); - // Sometimes we register equivalent type-tests that would - // result in basically the exact same error being reported to - // the user. Avoid that. let mut deduplicate_errors = FxIndexSet::default(); - for type_test in &self.type_tests { - debug!("check_type_test: {:?}", type_test); - - let generic_ty = type_test.generic_kind.to_ty(tcx); - if self.eval_verify_bound( - infcx, - generic_ty, - type_test.lower_bound, - &type_test.verify_bound, - ) { - continue; - } - - if let Some(propagated_outlives_requirements) = &mut propagated_outlives_requirements { - if self.try_promote_type_test(infcx, type_test, propagated_outlives_requirements) { - continue; + for error in self.type_tests.iter().filter_map(|tt| { + self.handle_type_test(infcx, tt, propagated_outlives_requirements.as_mut()) + }) { + match error { + e @ RegionErrorKind::TypeTestError { + lower_bound, + span, + generic_kind, + failed_due_to_placeholders: _, + } => { + let erased_generic_kind = infcx.tcx.erase_regions(generic_kind); + if deduplicate_errors.insert((lower_bound, span, erased_generic_kind)) { + debug!( + "check_type_test: reporting error for erased_generic_kind={:?}, \ + lower_bound_region={:?}, \ + span={:?}", + erased_generic_kind, lower_bound, span, + ); + errors_buffer.push(e); + } } - } - - // Type-test failed. Report the error. - let erased_generic_kind = infcx.tcx.erase_regions(type_test.generic_kind); - - // Skip duplicate-ish errors. - if deduplicate_errors.insert(( - erased_generic_kind, - type_test.lower_bound, - type_test.span, - )) { - debug!( - "check_type_test: reporting error for erased_generic_kind={:?}, \ - lower_bound_region={:?}, \ - type_test.span={:?}", - erased_generic_kind, type_test.lower_bound, type_test.span, - ); - - errors_buffer.push(RegionErrorKind::TypeTestError { type_test: type_test.clone() }); + t => unreachable!("Type-tests should not report `{t:?}'"), } } + + propagated_outlives_requirements } /// Invoked when we have some type-test (e.g., `T: 'X`) that we cannot @@ -977,42 +831,47 @@ impl<'tcx> RegionInferenceContext<'tcx> { infcx: &InferCtxt<'tcx>, type_test: &TypeTest<'tcx>, propagated_outlives_requirements: &mut Vec>, - ) -> bool { + tentative_error: RegionErrorKind<'tcx>, + ) -> Option> { let tcx = infcx.tcx; - let TypeTest { generic_kind, lower_bound, span: blame_span, verify_bound: _ } = *type_test; - let generic_ty = generic_kind.to_ty(tcx); + let generic_ty = type_test.generic_kind.to_ty(tcx); let Some(subject) = self.try_promote_type_test_subject(infcx, generic_ty) else { - return false; + return Some(tentative_error); }; - let r_scc = self.constraint_sccs.scc(lower_bound); - debug!( - "lower_bound = {:?} r_scc={:?} universe={:?}", - lower_bound, - r_scc, - self.constraint_sccs.annotation(r_scc).min_universe() - ); - // If the type test requires that `T: 'a` where `'a` is a - // placeholder from another universe, that effectively requires - // `T: 'static`, so we have to propagate that requirement. - // - // It doesn't matter *what* universe because the promoted `T` will - // always be in the root universe. - if let Some(p) = self.scc_values.placeholders_contained_in(r_scc).next() { - debug!("encountered placeholder in higher universe: {:?}, requiring 'static", p); + debug!("subject = {:?}", subject); + + let RegionErrorKind::TypeTestError { failed_due_to_placeholders, span, .. } = + tentative_error + else { + bug!("We should only see a TypeTestError from type test evaluation!"); + }; + + // If the type test required that `T: 'a` where `'a: 'static`, + // that effectively requires `T: 'static`, so we propagate that requirement. + if failed_due_to_placeholders { + debug!( + "Due to placeholder leaks, {generic_ty}: 'a, but 'a: 'static. Promoting that into {subject:?}!" + ); + let static_r = self.universal_regions().fr_static; propagated_outlives_requirements.push(ClosureOutlivesRequirement { subject, outlived_free_region: static_r, - blame_span, + blame_span: span, category: ConstraintCategory::Boring, }); // we can return here -- the code below might push add'l constraints // but they would all be weaker than this one. - return true; - } + return None; + }; + + let lower_bound = type_test.lower_bound; + let r_scc = self.constraint_sccs.scc(lower_bound); + + debug!(?lower_bound, ?r_scc); // For each region outlived by lower_bound find a non-local, // universal region (it may be the same region) and add it to @@ -1035,7 +894,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let requirement = ClosureOutlivesRequirement { subject, outlived_free_region: upper_bound, - blame_span, + blame_span: span, category: ConstraintCategory::Boring, }; debug!(?requirement, "adding closure requirement"); @@ -1047,7 +906,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // also be at least as large as some universal region, as the type test is otherwise // trivial. assert!(found_outlived_universal_region); - true + None } /// When we promote a type test `T: 'r`, we have to replace all region @@ -1094,6 +953,19 @@ impl<'tcx> RegionInferenceContext<'tcx> { Some(ClosureOutlivesSubject::Ty(ClosureOutlivesSubjectTy::bind(tcx, ty))) } + /// Returns the first named region reachable in the constraint graph + /// from `from`. For when you are really desperate for something + /// nameable. + #[instrument(level = "info", skip(self), ret)] + pub(crate) fn first_named_region_reached(&self, from: RegionVid) -> Option> { + self.find_constraint_path_to( + from, + |reached| self.region_definition(reached).external_name.is_some(), + true, + ) + .and_then(|x| self.region_definition(x.1).external_name) + } + /// Like `universal_upper_bound`, but returns an approximation more suitable /// for diagnostics. If `r` contains multiple disjoint universal regions /// (e.g. 'a and 'b in `fn foo<'a, 'b> { ... }`, we pick the lower-numbered region. @@ -1146,38 +1018,64 @@ impl<'tcx> RegionInferenceContext<'tcx> { } /// Tests if `test` is true when applied to `lower_bound` at - /// `point`. - fn eval_verify_bound( + /// `point`. If it isn't, return a failing `VerifyBound`. + fn eval_verify_bound<'vb>( &self, infcx: &InferCtxt<'tcx>, - generic_ty: Ty<'tcx>, - lower_bound: RegionVid, - verify_bound: &VerifyBound<'tcx>, - ) -> bool { - debug!("eval_verify_bound(lower_bound={:?}, verify_bound={:?})", lower_bound, verify_bound); + tt: &TypeTest<'tcx>, + verify_bound: &'vb VerifyBound<'tcx>, + ) -> Option<&'vb VerifyBound<'tcx>> { + let generic_ty = tt.generic_kind.to_ty(infcx.tcx); + let lower_bound = tt.lower_bound; match verify_bound { VerifyBound::IfEq(verify_if_eq_b) => { - self.eval_if_eq(infcx, generic_ty, lower_bound, *verify_if_eq_b) + (!self.eval_if_eq(infcx, generic_ty, lower_bound, *verify_if_eq_b)) + .then(|| verify_bound) } - VerifyBound::IsEmpty => { let lower_bound_scc = self.constraint_sccs.scc(lower_bound); - self.scc_values.elements_contained_in(lower_bound_scc).next().is_none() + self.scc_values + .elements_contained_in(lower_bound_scc) + .next() + .is_some() + .then(|| verify_bound) } - VerifyBound::OutlivedBy(r) => { let r_vid = self.to_region_vid(*r); - self.eval_outlives(r_vid, lower_bound) + (!self.eval_outlives(r_vid, lower_bound)).then(|| verify_bound) } - - VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| { - self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) - }), - - VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| { - self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) - }), + VerifyBound::AnyBound(verify_bounds) => { + let mut best_culprit: Option<&VerifyBound<'_>> = None; + for bound in verify_bounds.iter() { + if let Some(failing_bound) = self.eval_verify_bound(infcx, tt, bound) { + // SUBTLE DETAIL: + // We attribute the failure to an outlives-static rewrite + // from HRTBs as soon as one outlives-static fails. + // Of course, the original bound could still have failed, + // but adding the toughest constraint possible can't have + // helped, so we still blame that. + // This behaviour should, for the record, be equivalent + // to what was here before The Great Universe Elimination + // Refactor. + if matches!(failing_bound, VerifyBound::OutlivesStatic(_)) { + best_culprit = Some(bound) + } + } else { + return None; // It's enough for one bound to succeed + } + } + // If we got here no bound held. Blame our best candidate or, + // if we were empty, ourselves. Note that we can still have + // been rewritten into an empty any bound! + best_culprit.or(Some(verify_bound)) + } + VerifyBound::AllBounds(verify_bounds) => verify_bounds + .iter() + .find_map(|verify_bound| self.eval_verify_bound(infcx, tt, verify_bound)), + VerifyBound::OutlivesStatic(region) => (!self + .eval_outlives(self.to_region_vid(*region), self.universal_regions().fr_static)) + .then(|| verify_bound), } } @@ -1271,23 +1169,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { let sub_region_scc = self.constraint_sccs.scc(sub_region); let sup_region_scc = self.constraint_sccs.scc(sup_region); - if sub_region_scc == sup_region_scc { - debug!("{sup_region:?}: {sub_region:?} holds trivially; they are in the same SCC"); - return true; - } - - // If we are checking that `'sup: 'sub`, and `'sub` contains - // some placeholder that `'sup` cannot name, then this is only - // true if `'sup` outlives static. - if !self.universe_compatible(sub_region_scc, sup_region_scc) { - debug!( - "sub universe `{sub_region_scc:?}` is not nameable \ - by super `{sup_region_scc:?}`, promoting to static", - ); - - return self.eval_outlives(sup_region, self.universal_regions().fr_static); - } - // Both the `sub_region` and `sup_region` consist of the union // of some number of universal regions (along with the union // of various points in the CFG; ignore those points for @@ -1469,12 +1350,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } - /// The minimum universe of any variable reachable from this - /// SCC, inside or outside of it. - fn scc_universe(&self, scc: ConstraintSccIndex) -> UniverseIndex { - self.constraint_sccs().annotation(scc).min_universe() - } - /// Checks the final value for the free region `fr` to see if it /// grew too large. In particular, examine what `end(X)` points /// wound up in `fr`'s final value; for each `end(X)` where `X != @@ -1492,10 +1367,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { ) { let longer_fr_scc = self.constraint_sccs.scc(longer_fr); - // Because this free region must be in the ROOT universe, we - // know it cannot contain any bound universes. - assert!(self.scc_universe(longer_fr_scc).is_root()); - // Only check all of the relations for the main representative of each // SCC, otherwise just check that we outlive said representative. This // reduces the number of redundant relations propagated out of @@ -1617,31 +1488,29 @@ impl<'tcx> RegionInferenceContext<'tcx> { RegionRelationCheckResult::Error } + #[instrument(level = "debug", skip(self, errors_buffer))] fn check_bound_universal_region( &self, longer_fr: RegionVid, placeholder: ty::PlaceholderRegion, errors_buffer: &mut RegionErrors<'tcx>, ) { - debug!("check_bound_universal_region(fr={:?}, placeholder={:?})", longer_fr, placeholder,); - let longer_fr_scc = self.constraint_sccs.scc(longer_fr); debug!("check_bound_universal_region: longer_fr_scc={:?}", longer_fr_scc,); // If we have some bound universal region `'a`, then the only // elements it can contain is itself -- we don't know anything // else about it! - if let Some(error_element) = self - .scc_values - .elements_contained_in(longer_fr_scc) - .find(|e| *e != RegionElement::PlaceholderRegion(placeholder)) - { - // Stop after the first error, it gets too noisy otherwise, and does not provide more information. + if let Some(error_element) = self.scc_values.elements_contained_in(longer_fr_scc).next() { + debug!( + "check_bound_universal_region, error_element: {error_element:?} for placeholder {placeholder:?} in scc: {longer_fr_scc:?}" + ); errors_buffer.push(RegionErrorKind::BoundUniversalRegionError { longer_fr, error_element, placeholder, }); + // Stop after the first error, it gets too noisy otherwise, and does not provide more information. } else { debug!("check_bound_universal_region: all bounds satisfied"); } @@ -1662,74 +1531,26 @@ impl<'tcx> RegionInferenceContext<'tcx> { ?member_region_vid, value = ?self.region_value_str(member_region_vid), ); - let choice_regions = member_constraints.choice_regions(m_c_i); - debug!(?choice_regions); // Did the member region wind up equal to any of the option regions? - if let Some(o) = - choice_regions.iter().find(|&&o_r| self.eval_equal(o_r, m_c.member_region_vid)) + if let Some(o) = member_constraints + .choice_regions(m_c_i) + .find(|&o_r| self.eval_equal(o_r, m_c.member_region_vid)) { - debug!("evaluated as equal to {:?}", o); + debug!("Successful evaluation: {member_region_vid:?} == {o:?}"); continue; - } + }; // If not, report an error. let member_region = ty::Region::new_var(infcx.tcx, member_region_vid); - errors_buffer.push(RegionErrorKind::UnexpectedHiddenRegion { + let new_error = RegionErrorKind::UnexpectedHiddenRegion { span: m_c.definition_span, hidden_ty: m_c.hidden_ty, key: m_c.key, member_region, - }); - } - } - - /// We have a constraint `fr1: fr2` that is not satisfied, where - /// `fr2` represents some universal region. Here, `r` is some - /// region where we know that `fr1: r` and this function has the - /// job of determining whether `r` is "to blame" for the fact that - /// `fr1: fr2` is required. - /// - /// This is true under two conditions: - /// - /// - `r == fr2` - /// - `fr2` is `'static` and `r` is some placeholder in a universe - /// that cannot be named by `fr1`; in that case, we will require - /// that `fr1: 'static` because it is the only way to `fr1: r` to - /// be satisfied. (See `add_incompatible_universe`.) - pub(crate) fn provides_universal_region( - &self, - r: RegionVid, - fr1: RegionVid, - fr2: RegionVid, - ) -> bool { - debug!("provides_universal_region(r={:?}, fr1={:?}, fr2={:?})", r, fr1, fr2); - let result = { - r == fr2 || { - fr2 == self.universal_regions().fr_static && self.cannot_name_placeholder(fr1, r) - } - }; - debug!("provides_universal_region: result = {:?}", result); - result - } - - /// If `r2` represents a placeholder region, then this returns - /// `true` if `r1` cannot name that placeholder in its - /// value; otherwise, returns `false`. - pub(crate) fn cannot_name_placeholder(&self, r1: RegionVid, r2: RegionVid) -> bool { - match self.definitions[r2].origin { - NllRegionVariableOrigin::Placeholder(placeholder) => { - let r1_universe = self.definitions[r1].universe; - debug!( - "cannot_name_value_of: universe1={r1_universe:?} placeholder={:?}", - placeholder - ); - r1_universe.cannot_name(placeholder.universe) - } - - NllRegionVariableOrigin::FreeRegion | NllRegionVariableOrigin::Existential { .. } => { - false - } + }; + debug!(?new_error); + errors_buffer.push(new_error); } } @@ -1740,25 +1561,34 @@ impl<'tcx> RegionInferenceContext<'tcx> { fr1_origin: NllRegionVariableOrigin, fr2: RegionVid, ) -> (ConstraintCategory<'tcx>, ObligationCause<'tcx>) { - let BlameConstraint { category, cause, .. } = self - .best_blame_constraint(fr1, fr1_origin, |r| self.provides_universal_region(r, fr1, fr2)) - .0; + let BlameConstraint { category, cause, .. } = + self.best_blame_constraint(fr1, fr1_origin, fr2).0; (category, cause) } + pub(crate) fn constraint_path_between_regions( + &self, + from_region: RegionVid, + to_region: RegionVid, + ) -> Option<(Vec>, RegionVid)> { + self.find_constraint_path_to(from_region, |to| to == to_region, true) + } + /// Walks the graph of constraints (where `'a: 'b` is considered - /// an edge `'a -> 'b`) to find all paths from `from_region` to - /// `to_region`. The paths are accumulated into the vector - /// `results`. The paths are stored as a series of - /// `ConstraintIndex` values -- in other words, a list of *edges*. - /// + /// an edge `'a -> 'b`) to find a path from `from_region` to + /// the first region `R` for which the predicate function + /// `target_test` returns `true`. /// Returns: a series of constraints as well as the region `R` /// that passed the target test. + /// If `include_static_outlives_all` is `true`, then the synthetic + /// outlives constraints `'static -> a` for every region `a` are + /// considered in the search, otherwise they are ignored. #[instrument(skip(self, target_test), ret)] - pub(crate) fn find_constraint_paths_between_regions( + pub(crate) fn find_constraint_path_to( &self, from_region: RegionVid, target_test: impl Fn(RegionVid) -> bool, + include_static_outlives_all: bool, ) -> Option<(Vec>, RegionVid)> { let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions); context[from_region] = Trace::StartRegion; @@ -1773,7 +1603,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { while let Some(r) = deque.pop_front() { debug!( - "find_constraint_paths_between_regions: from_region={:?} r={:?} value={}", + "find_constraint_path_to: from_region={:?} r={:?} value={}", from_region, r, self.region_value_str(r), @@ -1851,7 +1681,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // If this is the `'static` region and the graph's direction is normal, then set up the // Edges iterator to return all regions (#53178). - if r == fr_static && self.constraint_graph.is_normal() { + if r == fr_static && self.constraint_graph.is_normal() && include_static_outlives_all { for sub in self.constraint_graph.outgoing_edges_from_static() { handle_trace(sub, Trace::FromStatic(sub)); } @@ -1859,10 +1689,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { let edges = self.constraint_graph.outgoing_edges_from_graph(r, &self.constraints); // This loop can be hot. for constraint in edges { - if matches!(constraint.category, ConstraintCategory::IllegalUniverse) { - debug!("Ignoring illegal universe constraint: {constraint:?}"); - continue; - } debug_assert_eq!(constraint.sup, r); handle_trace(constraint.sub, Trace::FromGraph(constraint)); } @@ -1885,36 +1711,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { #[instrument(skip(self), level = "trace", ret)] pub(crate) fn find_sub_region_live_at(&self, fr1: RegionVid, location: Location) -> RegionVid { trace!(scc = ?self.constraint_sccs.scc(fr1)); - trace!(universe = ?self.region_universe(fr1)); - self.find_constraint_paths_between_regions(fr1, |r| { + self.find_constraint_path_to(fr1, |r| { // First look for some `r` such that `fr1: r` and `r` is live at `location` trace!(?r, liveness_constraints=?self.liveness_constraints.pretty_print_live_points(r)); self.liveness_constraints.is_live_at(r, location) - }) - .or_else(|| { - // If we fail to find that, we may find some `r` such that - // `fr1: r` and `r` is a placeholder from some universe - // `fr1` cannot name. This would force `fr1` to be - // `'static`. - self.find_constraint_paths_between_regions(fr1, |r| { - self.cannot_name_placeholder(fr1, r) - }) - }) - .or_else(|| { - // If we fail to find THAT, it may be that `fr1` is a - // placeholder that cannot "fit" into its SCC. In that - // case, there should be some `r` where `fr1: r` and `fr1` is a - // placeholder that `r` cannot name. We can blame that - // edge. - // - // Remember that if `R1: R2`, then the universe of R1 - // must be able to name the universe of R2, because R2 will - // be at least `'empty(Universe(R2))`, and `R1` must be at - // larger than that. - self.find_constraint_paths_between_regions(fr1, |r| { - self.cannot_name_placeholder(r, fr1) - }) - }) + }, + true + ) .map(|(_path, r)| r) .unwrap() } @@ -1928,14 +1731,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { match *element { RegionElement::Location(l) => self.find_sub_region_live_at(longer_fr, l), RegionElement::RootUniversalRegion(r) => r, - RegionElement::PlaceholderRegion(error_placeholder) => self - .definitions - .iter_enumerated() - .find_map(|(r, definition)| match definition.origin { - NllRegionVariableOrigin::Placeholder(p) if p == error_placeholder => Some(r), - _ => None, - }) - .unwrap(), } } @@ -1954,28 +1749,59 @@ impl<'tcx> RegionInferenceContext<'tcx> { &self.universal_region_relations.universal_regions } + /// Find a path of outlives constraints from `from` to `to`, + /// taking placeholder blame constraints into account, e.g. + /// if there is a relationship where `r1` reaches `r2` and + /// r2 has a larger universe or if r1 and r2 both come from + /// placeholder regions. + /// + /// Returns the path and the target region, which may or may + /// not be the original `to`. It panics if there is no such + /// path. + fn path_to_modulo_placeholders( + &self, + from: RegionVid, + to: RegionVid, + ) -> (Vec>, RegionVid) { + let path = self.constraint_path_between_regions(from, to).unwrap().0; + + // If we are looking for a path to 'static, and we are passing + // through a constraint synthesised from an illegal placeholder + // relation, redirect the search to the placeholder to blame. + // FIXME: the performance of this is Not Great, and the logic + // should be folded into the search itself if possible. + for constraint in path.iter() { + let ConstraintCategory::IllegalPlaceholder(cl_fr, culprit) = constraint.category else { + continue; + }; + + debug!("{culprit:?} is the reason {from:?}: 'static!"); + // FIXME: think: this may be for transitive reasons and + // we may have to do this arbitrarily many times. Or may we? + return self.find_constraint_path_to(cl_fr, |r| r == culprit, false).unwrap(); + } + + // No funny business; just return the path! + (path, to) + } + /// Tries to find the best constraint to blame for the fact that - /// `R: from_region`, where `R` is some region that meets - /// `target_test`. This works by following the constraint graph, + /// `to_region: from_region`. + /// This works by following the constraint graph, /// creating a constraint path that forces `R` to outlive /// `from_region`, and then finding the best choices within that /// path to blame. - #[instrument(level = "debug", skip(self, target_test))] + #[instrument(level = "debug", skip(self))] pub(crate) fn best_blame_constraint( &self, from_region: RegionVid, from_region_origin: NllRegionVariableOrigin, - target_test: impl Fn(RegionVid) -> bool, + to_region: RegionVid, ) -> (BlameConstraint<'tcx>, Vec>) { - // Find all paths - let (path, target_region) = self - .find_constraint_paths_between_regions(from_region, target_test) - .or_else(|| { - self.find_constraint_paths_between_regions(from_region, |r| { - self.cannot_name_placeholder(from_region, r) - }) - }) - .unwrap(); + assert!(from_region != to_region, "Trying to blame a region for itself!"); + + let (path, new_to_region) = self.path_to_modulo_placeholders(from_region, to_region); + debug!( "path={:#?}", path.iter() @@ -2006,8 +1832,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { }) .unwrap_or_else(|| ObligationCauseCode::Misc); - // When reporting an error, there is typically a chain of constraints leading from some - // "source" region which must outlive some "target" region. // In most cases, we prefer to "blame" the constraints closer to the target -- // but there is one exception. When constraints arise from higher-ranked subtyping, // we generally prefer to blame the source value, @@ -2078,7 +1902,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { ConstraintCategory::Cast { unsize_to: Some(unsize_ty), is_implicit_coercion: true, - } if target_region == self.universal_regions().fr_static + } if new_to_region == self.universal_regions().fr_static // Mirror the note's condition, to minimize how often this diverts blame. && let ty::Adt(_, args) = unsize_ty.kind() && args.iter().any(|arg| arg.as_type().is_some_and(|ty| ty.is_trait())) @@ -2115,8 +1939,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { // `BoringNoLocation` constraints can point to user-written code, but are less // specific, and are not used for relations that would make sense to blame. ConstraintCategory::BoringNoLocation => 6, + ConstraintCategory::IllegalPlaceholder(_, _) => 7, // Do not blame internal constraints. - ConstraintCategory::IllegalUniverse => 7, ConstraintCategory::Internal => 8, }; @@ -2198,7 +2022,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// This can be used to quickly under-approximate the regions which are equal to each other /// and their relative orderings. // This is `pub` because it's used by unstable external borrowck data users, see `consumers.rs`. - pub fn constraint_sccs(&self) -> &ConstraintSccs { + pub fn constraint_sccs(&self) -> &Sccs { &self.constraint_sccs } @@ -2208,7 +2032,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } /// Returns the representative `RegionVid` for a given SCC. - /// See `RegionTracker` for how a region variable ID is chosen. + /// See [`crate::eliminate_placeholders`] for how a region variable ID is chosen. /// /// It is a hacky way to manage checking regions for equality, /// since we can 'canonicalize' each region to the representative @@ -2216,7 +2040,22 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// they *must* be equal (though not having the same repr does not /// mean they are unequal). fn scc_representative(&self, scc: ConstraintSccIndex) -> RegionVid { - self.constraint_sccs.annotation(scc).representative + self.scc_representatives[scc].rvid() + } + + /// If the representative of an SCC is a placeholder, return + /// its originating `PlaceholderRegion`. + pub(crate) fn placeholder_representative( + &self, + scc: ConstraintSccIndex, + ) -> Option { + if let Representative::Placeholder(r) = self.scc_representatives[scc] + && let NllRegionVariableOrigin::Placeholder(p) = self.definitions[r].origin + { + Some(p) + } else { + None + } } pub(crate) fn liveness_constraints(&self) -> &LivenessValues { @@ -2239,17 +2078,17 @@ impl<'tcx> RegionInferenceContext<'tcx> { } impl<'tcx> RegionDefinition<'tcx> { - fn new(universe: ty::UniverseIndex, rv_origin: RegionVariableOrigin) -> Self { + pub(crate) fn new(rv_info: &RegionVariableInfo) -> Self { // Create a new region definition. Note that, for free // regions, the `external_name` field gets updated later in // `init_free_and_bound_regions`. - let origin = match rv_origin { + let origin = match rv_info.origin { RegionVariableOrigin::Nll(origin) => origin, _ => NllRegionVariableOrigin::Existential { from_forall: false }, }; - Self { origin, universe, external_name: None } + Self { origin, universe: rv_info.universe, external_name: None } } } diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs index 550c57338d301..b6b77221cefba 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs @@ -188,32 +188,22 @@ impl<'tcx> RegionInferenceContext<'tcx> { { fold_regions(tcx, ty, |region, _| match region.kind() { ty::ReVar(vid) => { - let scc = self.constraint_sccs.scc(vid); - - // Special handling of higher-ranked regions. - if !self.scc_universe(scc).is_root() { - match self.scc_values.placeholders_contained_in(scc).enumerate().last() { - // If the region contains a single placeholder then they're equal. - Some((0, placeholder)) => { - return ty::Region::new_placeholder(tcx, placeholder); - } - - // Fallback: this will produce a cryptic error message. - _ => return region, - } - } - // Find something that we can name let upper_bound = self.approx_universal_upper_bound(vid); if let Some(universal_region) = self.definitions[upper_bound].external_name { return universal_region; } + let scc = self.constraint_sccs.scc(vid); + + if let Some(representative) = self.placeholder_representative(scc) { + return ty::Region::new_placeholder(tcx, representative); + } + // Nothing exact found, so we pick a named upper bound, if there's only one. // If there's >1 universal region, then we probably are dealing w/ an intersection // region which cannot be mapped back to a universal. // FIXME: We could probably compute the LUB if there is one. - let scc = self.constraint_sccs.scc(vid); let upper_bounds: Vec<_> = self .rev_scc_graph .as_ref() diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index f1427218cdb02..e6c28c5d245e4 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -29,10 +29,6 @@ pub(crate) enum RegionElement { /// A universally quantified region from the root universe (e.g., /// a lifetime parameter). RootUniversalRegion(RegionVid), - - /// A placeholder (e.g., instantiated from a `for<'a> fn(&'a u32)` - /// type). - PlaceholderRegion(ty::PlaceholderRegion), } /// Records the CFG locations where each region is live. When we initially compute liveness, we use @@ -204,21 +200,6 @@ impl PlaceholderIndices { let (index, _) = self.indices.insert_full(placeholder); index.into() } - - pub(crate) fn lookup_index(&self, placeholder: ty::PlaceholderRegion) -> PlaceholderIndex { - self.indices.get_index_of(&placeholder).unwrap().into() - } - - pub(crate) fn lookup_placeholder( - &self, - placeholder: PlaceholderIndex, - ) -> ty::PlaceholderRegion { - self.indices[placeholder.index()] - } - - pub(crate) fn len(&self) -> usize { - self.indices.len() - } } /// Stores the full values for a set of regions (in contrast to @@ -241,32 +222,20 @@ impl PlaceholderIndices { /// it would also contain various points from within the function. pub(crate) struct RegionValues { location_map: Rc, - placeholder_indices: PlaceholderIndices, points: SparseIntervalMatrix, free_regions: SparseBitMatrix, - - /// Placeholders represent bound regions -- so something like `'a` - /// in `for<'a> fn(&'a u32)`. - placeholders: SparseBitMatrix, } impl RegionValues { /// Creates a new set of "region values" that tracks causal information. /// Each of the regions in num_region_variables will be initialized with an /// empty set of points and no causal information. - pub(crate) fn new( - location_map: Rc, - num_universal_regions: usize, - placeholder_indices: PlaceholderIndices, - ) -> Self { + pub(crate) fn new(location_map: Rc, num_universal_regions: usize) -> Self { let num_points = location_map.num_points(); - let num_placeholders = placeholder_indices.len(); Self { location_map, points: SparseIntervalMatrix::new(num_points), - placeholder_indices, free_regions: SparseBitMatrix::new(num_universal_regions), - placeholders: SparseBitMatrix::new(num_placeholders), } } @@ -285,9 +254,7 @@ impl RegionValues { /// Adds all elements in `r_from` to `r_to` (because e.g., `r_to: /// r_from`). pub(crate) fn add_region(&mut self, r_to: N, r_from: N) -> bool { - self.points.union_rows(r_from, r_to) - | self.free_regions.union_rows(r_from, r_to) - | self.placeholders.union_rows(r_from, r_to) + self.points.union_rows(r_from, r_to) | self.free_regions.union_rows(r_from, r_to) } /// Returns `true` if the region `r` contains the given element. @@ -354,28 +321,16 @@ impl RegionValues { } /// Returns all the elements contained in a given region's value. - pub(crate) fn placeholders_contained_in( - &self, + pub(crate) fn elements_contained_in<'a>( + &'a self, r: N, - ) -> impl Iterator { - self.placeholders - .row(r) - .into_iter() - .flat_map(|set| set.iter()) - .map(move |p| self.placeholder_indices.lookup_placeholder(p)) - } - - /// Returns all the elements contained in a given region's value. - pub(crate) fn elements_contained_in(&self, r: N) -> impl Iterator { + ) -> impl Iterator + 'a { let points_iter = self.locations_outlived_by(r).map(RegionElement::Location); let free_regions_iter = self.universal_regions_outlived_by(r).map(RegionElement::RootUniversalRegion); - let placeholder_universes_iter = - self.placeholders_contained_in(r).map(RegionElement::PlaceholderRegion); - - points_iter.chain(free_regions_iter).chain(placeholder_universes_iter) + points_iter.chain(free_regions_iter) } /// Returns a "pretty" string value of the region. Meant for debugging. @@ -412,18 +367,6 @@ impl ToElementIndex for RegionVid { } } -impl ToElementIndex for ty::PlaceholderRegion { - fn add_to_row(self, values: &mut RegionValues, row: N) -> bool { - let index = values.placeholder_indices.lookup_index(self); - values.placeholders.insert(row, index) - } - - fn contained_in_row(self, values: &RegionValues, row: N) -> bool { - let index = values.placeholder_indices.lookup_index(self); - values.placeholders.contains(row, index) - } -} - /// For debugging purposes, returns a pretty-printed string of the given points. pub(crate) fn pretty_print_points( location_map: &DenseLocationMap, @@ -483,17 +426,6 @@ fn pretty_print_region_elements(elements: impl IntoIterator { - if let Some((location1, location2)) = open_location { - push_sep(&mut result); - push_location_range(&mut result, location1, location2); - open_location = None; - } - - push_sep(&mut result); - result.push_str(&format!("{placeholder:?}")); - } } } diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index 57516565147eb..d99aa972bdea3 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -14,7 +14,7 @@ use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; use tracing::{debug, instrument}; use crate::constraints::OutlivesConstraint; -use crate::region_infer::TypeTest; +use crate::region_infer::{TypeTest, TypeTestOrigin}; use crate::type_check::{Locations, MirTypeckRegionConstraints}; use crate::universal_regions::UniversalRegions; use crate::{ClosureOutlivesSubject, ClosureRegionRequirements, ConstraintCategory}; @@ -219,7 +219,12 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { verify_bound: VerifyBound<'tcx>, ) -> TypeTest<'tcx> { let lower_bound = self.to_region_vid(region); - TypeTest { generic_kind, lower_bound, span: self.span, verify_bound } + TypeTest { + generic_kind, + lower_bound, + source: TypeTestOrigin::Code(self.span), + verify_bound, + } } fn to_region_vid(&mut self, r: ty::Region<'tcx>) -> ty::RegionVid { diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 3c00b819a96bb..c07ad4e3752cc 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -245,14 +245,14 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> { /// /// To keep everything in sync, do not insert this set /// directly. Instead, use the `placeholder_region` helper. - pub(crate) placeholder_indices: PlaceholderIndices, + placeholder_indices: PlaceholderIndices, /// Each time we add a placeholder to `placeholder_indices`, we /// also create a corresponding "representative" region vid for /// that wraps it. This vector tracks those. This way, when we /// convert the same `ty::RePlaceholder(p)` twice, we can map to /// the same underlying `RegionVid`. - pub(crate) placeholder_index_to_region: IndexVec>, + placeholder_index_to_region: IndexVec>, /// In general, the type-checker is not responsible for enforcing /// liveness constraints; this job falls to the region inferencer, diff --git a/compiler/rustc_data_structures/src/graph/scc/mod.rs b/compiler/rustc_data_structures/src/graph/scc/mod.rs index e7c4ea3daae45..c0fe9e8ff93e9 100644 --- a/compiler/rustc_data_structures/src/graph/scc/mod.rs +++ b/compiler/rustc_data_structures/src/graph/scc/mod.rs @@ -13,7 +13,7 @@ use std::fmt::Debug; use std::ops::Range; use rustc_index::{Idx, IndexSlice, IndexVec}; -use tracing::{debug, instrument}; +use tracing::{debug, instrument, trace}; use crate::fx::FxHashSet; use crate::graph::vec_graph::VecGraph; @@ -48,6 +48,20 @@ pub trait Annotation: Debug + Copy { } } +/// An accumulator for annotations. +pub trait Annotations { + fn new(&self, element: N) -> A; + fn annotate_scc(&mut self, scc: S, annotation: A); +} + +/// The nil annotation accumulator, which does nothing. +impl Annotations for () { + fn new(&self, _element: N) -> () { + () + } + fn annotate_scc(&mut self, _scc: S, _annotation: ()) {} +} + /// The empty annotation, which does nothing. impl Annotation for () { fn merge_reached(self, _other: Self) -> Self { @@ -62,23 +76,20 @@ impl Annotation for () { /// the index type for the graph nodes and `S` is the index type for /// the SCCs. We can map from each node to the SCC that it /// participates in, and we also have the successors of each SCC. -pub struct Sccs { +pub struct Sccs { /// For each node, what is the SCC index of the SCC to which it /// belongs. scc_indices: IndexVec, /// Data about all the SCCs. - scc_data: SccData, + scc_data: SccData, } /// Information about an invidividual SCC node. -struct SccDetails { +struct SccDetails { /// For this SCC, the range of `all_successors` where its /// successors can be found. range: Range, - - /// User-specified metadata about the SCC. - annotation: A, } // The name of this struct should discourage you from making it public and leaking @@ -87,10 +98,10 @@ struct SccDetails { // is difficult when it's publicly inspectable. // // Obey the law of Demeter! -struct SccData { +struct SccData { /// Maps SCC indices to their metadata, including /// offsets into `all_successors`. - scc_details: IndexVec>, + scc_details: IndexVec, /// Contains the successors for all the Sccs, concatenated. The /// range of indices corresponding to a given SCC is found in its @@ -98,24 +109,18 @@ struct SccData { all_successors: Vec, } -impl Sccs { +impl Sccs { /// Compute SCCs without annotations. pub fn new(graph: &impl Successors) -> Self { - Self::new_with_annotation(graph, |_| ()) + Self::new_with_annotation(graph, &mut ()) } -} -impl Sccs { /// Compute SCCs and annotate them with a user-supplied annotation - pub fn new_with_annotation A>( + pub fn new_with_annotation>( graph: &impl Successors, - to_annotation: F, + annotations: &mut AA, ) -> Self { - SccsConstruction::construct(graph, to_annotation) - } - - pub fn annotation(&self, scc: S) -> A { - self.scc_data.annotation(scc) + SccsConstruction::construct(graph, annotations) } pub fn scc_indices(&self) -> &IndexSlice { @@ -136,7 +141,13 @@ impl Sccs { pub fn all_sccs(&self) -> impl Iterator + 'static { (0..self.scc_data.len()).map(S::new) } - + /* + /// Returns an iterator over the SCC annotations in the graph + /// The order is the same as `all_sccs()`, dependency order. + pub fn all_annotations(&self, annotations: &A) -> impl Iterator + use<'_, N, S, A> { + self.all_sccs().map(|scc| (scc, self.annotation(scc))) + } + */ /// Returns the SCC to which a node `r` belongs. pub fn scc(&self, r: N) -> S { self.scc_indices[r] @@ -160,7 +171,7 @@ impl Sccs { } } -impl DirectedGraph for Sccs { +impl DirectedGraph for Sccs { type Node = S; fn num_nodes(&self) -> usize { @@ -168,19 +179,19 @@ impl DirectedGraph for Sccs { } } -impl NumEdges for Sccs { +impl NumEdges for Sccs { fn num_edges(&self) -> usize { self.scc_data.all_successors.len() } } -impl Successors for Sccs { +impl Successors for Sccs { fn successors(&self, node: S) -> impl Iterator { self.successors(node).iter().cloned() } } -impl SccData { +impl SccData { /// Number of SCCs, fn len(&self) -> usize { self.scc_details.len() @@ -192,9 +203,8 @@ impl SccData { } /// Creates a new SCC with `successors` as its successors and - /// the maximum weight of its internal nodes `scc_max_weight` and /// returns the resulting index. - fn create_scc(&mut self, successors: impl IntoIterator, annotation: A) -> S { + fn create_scc(&mut self, successors: impl IntoIterator) -> S { // Store the successors on `scc_successors_vec`, remembering // the range of indices. let all_successors_start = self.all_successors.len(); @@ -202,28 +212,23 @@ impl SccData { let all_successors_end = self.all_successors.len(); debug!( - "create_scc({:?}) successors={:?}, annotation={:?}", + "create_scc({:?}) successors={:?}", self.len(), &self.all_successors[all_successors_start..all_successors_end], - annotation ); let range = all_successors_start..all_successors_end; - let metadata = SccDetails { range, annotation }; + let metadata = SccDetails { range }; self.scc_details.push(metadata) } - - fn annotation(&self, scc: S) -> A { - self.scc_details[scc].annotation - } } -struct SccsConstruction<'c, G, S, A, F> +struct SccsConstruction<'c, 'a, G, S, A, AA> where G: DirectedGraph + Successors, S: Idx, A: Annotation, - F: Fn(G::Node) -> A, + AA: Annotations, { graph: &'c G, @@ -247,11 +252,9 @@ where /// around between successors to amortize memory allocation costs. duplicate_set: FxHashSet, - scc_data: SccData, + scc_data: SccData, - /// A function that constructs an initial SCC annotation - /// out of a single node. - to_annotation: F, + annotations: &'a mut AA, } #[derive(Copy, Clone, Debug)] @@ -299,12 +302,12 @@ enum WalkReturn { Complete { scc_index: S, annotation: A }, } -impl<'c, G, S, A, F> SccsConstruction<'c, G, S, A, F> +impl<'c, 'a, G, S, A, AA> SccsConstruction<'c, 'a, G, S, A, AA> where G: DirectedGraph + Successors, S: Idx, - F: Fn(G::Node) -> A, A: Annotation, + AA: Annotations, { /// Identifies SCCs in the graph `G` and computes the resulting /// DAG. This uses a variant of [Tarjan's @@ -320,7 +323,7 @@ where /// Additionally, we keep track of a current annotation of the SCC. /// /// [wikipedia]: https://bit.ly/2EZIx84 - fn construct(graph: &'c G, to_annotation: F) -> Sccs { + fn construct(graph: &'c G, annotations: &'a mut AA) -> Sccs { let num_nodes = graph.num_nodes(); let mut this = Self { @@ -330,7 +333,7 @@ where successors_stack: Vec::new(), scc_data: SccData { scc_details: IndexVec::new(), all_successors: Vec::new() }, duplicate_set: FxHashSet::default(), - to_annotation, + annotations, }; let scc_indices = graph @@ -408,7 +411,7 @@ where // a potentially derived version of the root state for non-root nodes in the chain. let (root_state, assigned_state) = { loop { - debug!("find_state(r = {node:?} in state {:?})", self.node_states[node]); + trace!("find_state(r = {node:?} in state {:?})", self.node_states[node]); match self.node_states[node] { // This must have been the first and only state since it is unexplored*; // no update needed! * Unless there is a bug :') @@ -482,7 +485,7 @@ where if previous_node == node { return root_state; } - debug!("Compressing {node:?} down to {previous_node:?} with state {assigned_state:?}"); + trace!("Compressing {node:?} down to {previous_node:?} with state {assigned_state:?}"); // Update to previous node in the link. match self.node_states[previous_node] { @@ -507,9 +510,9 @@ where /// Call this method when `inspect_node` has returned `None`. Having the /// caller decide avoids mutual recursion between the two methods and allows /// us to maintain an allocated stack for nodes on the path between calls. - #[instrument(skip(self, initial), level = "debug")] + #[instrument(skip(self, initial), level = "trace")] fn walk_unvisited_node(&mut self, initial: G::Node) -> WalkReturn { - debug!("Walk unvisited node: {initial:?}"); + trace!("Walk unvisited node: {initial:?}"); struct VisitingNodeFrame { node: G::Node, successors: Option, @@ -537,7 +540,7 @@ where successors_len: 0, min_cycle_root: initial, successor_node: initial, - current_component_annotation: (self.to_annotation)(initial), + current_component_annotation: self.annotations.new(initial), }]; let mut return_value = None; @@ -556,11 +559,7 @@ where let node = *node; let depth = *depth; - // node is definitely in the current component, add it to the annotation. - if node != initial { - current_component_annotation.update_scc((self.to_annotation)(node)); - } - debug!( + trace!( "Visiting {node:?} at depth {depth:?}, annotation: {current_component_annotation:?}" ); @@ -568,7 +567,7 @@ where Some(successors) => successors, None => { // This None marks that we still have the initialize this node's frame. - debug!(?depth, ?node); + trace!(?depth, ?node); debug_assert_matches!(self.node_states[node], NodeState::NotVisited); @@ -598,7 +597,7 @@ where return_value.take().into_iter().map(|walk| (*successor_node, Some(walk))); let successor_walk = successors.map(|successor_node| { - debug!(?node, ?successor_node); + trace!(?node, ?successor_node); (successor_node, self.inspect_node(successor_node)) }); for (successor_node, walk) in returned_walk.chain(successor_walk) { @@ -609,13 +608,13 @@ where min_depth: successor_min_depth, annotation: successor_annotation, }) => { - debug!( + trace!( "Cycle found from {node:?}, minimum depth: {successor_min_depth:?}, annotation: {successor_annotation:?}" ); // Track the minimum depth we can reach. assert!(successor_min_depth <= depth); if successor_min_depth < *min_depth { - debug!(?node, ?successor_min_depth); + trace!(?node, ?successor_min_depth); *min_depth = successor_min_depth; *min_cycle_root = successor_node; } @@ -627,20 +626,20 @@ where scc_index: successor_scc_index, annotation: successor_annotation, }) => { - debug!( + trace!( "Complete; {node:?} is root of complete-visited SCC idx {successor_scc_index:?} with annotation {successor_annotation:?}" ); // Push the completed SCC indices onto // the `successors_stack` for later. - debug!(?node, ?successor_scc_index); + trace!(?node, ?successor_scc_index); successors_stack.push(successor_scc_index); current_component_annotation.update_reachable(successor_annotation); } // `node` has no more (direct) successors; search recursively. None => { let depth = depth + 1; - debug!("Recursing down into {successor_node:?} at depth {depth:?}"); - debug!(?depth, ?successor_node); + trace!("Recursing down into {successor_node:?} at depth {depth:?}"); + trace!(?depth, ?successor_node); // Remember which node the return value will come from. frame.successor_node = successor_node; // Start a new stack frame, then step into it. @@ -652,14 +651,14 @@ where min_depth: depth, min_cycle_root: successor_node, successor_node, - current_component_annotation: (self.to_annotation)(successor_node), + current_component_annotation: self.annotations.new(successor_node), }); continue 'recurse; } } } - debug!("Finished walk from {node:?} with annotation: {current_component_annotation:?}"); + trace!("Finished walk from {node:?} with annotation: {current_component_annotation:?}"); // Completed walk, remove `node` from the stack. let r = self.node_stack.pop(); @@ -691,8 +690,9 @@ where debug!("Creating SCC rooted in {node:?} with successor {:?}", frame.successor_node); - let scc_index = - self.scc_data.create_scc(deduplicated_successors, current_component_annotation); + let scc_index = self.scc_data.create_scc(deduplicated_successors); + + self.annotations.annotate_scc(scc_index, current_component_annotation); self.node_states[node] = NodeState::InCycle { scc_index, annotation: current_component_annotation }; diff --git a/compiler/rustc_data_structures/src/graph/scc/tests.rs b/compiler/rustc_data_structures/src/graph/scc/tests.rs index 373f87bfdbcfa..e388e7b6680af 100644 --- a/compiler/rustc_data_structures/src/graph/scc/tests.rs +++ b/compiler/rustc_data_structures/src/graph/scc/tests.rs @@ -5,8 +5,28 @@ use crate::graph::tests::TestGraph; #[derive(Copy, Clone, Debug)] struct MaxReached(usize); -type UsizeSccs = Sccs; -type MaxReachedSccs = Sccs; +struct Maxes(IndexVec, fn(usize) -> usize); +type UsizeSccs = Sccs; + +impl Annotations for Maxes { + fn new(&self, element: usize) -> MaxReached { + MaxReached(self.1(element)) + } + + fn annotate_scc(&mut self, scc: usize, annotation: MaxReached) { + let i = self.0.push(annotation); + assert!(i == scc); + } +} + +impl Maxes { + fn annotation(&self, scc: usize) -> MaxReached { + self.0[scc] + } + fn new(mapping: fn(usize) -> usize) -> Self { + Self(IndexVec::new(), mapping) + } +} impl Annotation for MaxReached { fn merge_scc(self, other: Self) -> Self { @@ -14,7 +34,7 @@ impl Annotation for MaxReached { } fn merge_reached(self, other: Self) -> Self { - self.merge_scc(other) + Self(std::cmp::max(other.0, self.0)) } } @@ -24,17 +44,29 @@ impl PartialEq for MaxReached { } } -impl MaxReached { - fn from_usize(nr: usize) -> Self { - Self(nr) - } -} - #[derive(Copy, Clone, Debug)] struct MinMaxIn { min: usize, max: usize, } +struct MinMaxes(IndexVec, fn(usize) -> MinMaxIn); + +impl MinMaxes { + fn annotation(&self, scc: usize) -> MinMaxIn { + self.0[scc] + } +} + +impl Annotations for MinMaxes { + fn new(&self, element: usize) -> MinMaxIn { + self.1(element) + } + + fn annotate_scc(&mut self, scc: usize, annotation: MinMaxIn) { + let i = self.0.push(annotation); + assert!(i == scc); + } +} impl Annotation for MinMaxIn { fn merge_scc(self, other: Self) -> Self { @@ -261,67 +293,68 @@ fn bench_sccc(b: &mut test::Bencher) { #[test] fn test_max_self_loop() { let graph = TestGraph::new(0, &[(0, 0)]); - let sccs: MaxReachedSccs = - Sccs::new_with_annotation(&graph, |n| if n == 0 { MaxReached(17) } else { MaxReached(0) }); - assert_eq!(sccs.annotation(0), 17); + let mut annotations = Maxes(IndexVec::new(), |n| if n == 0 { 17 } else { 0 }); + Sccs::new_with_annotation(&graph, &mut annotations); + assert_eq!(annotations.0[0], 17); } #[test] fn test_max_branch() { let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (2, 4)]); - let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, MaxReached::from_usize); - assert_eq!(sccs.annotation(sccs.scc(0)), 4); - assert_eq!(sccs.annotation(sccs.scc(1)), 3); - assert_eq!(sccs.annotation(sccs.scc(2)), 4); + let mut annotations = Maxes(IndexVec::new(), |n| n); + let sccs = Sccs::new_with_annotation(&graph, &mut annotations); + assert_eq!(annotations.0[sccs.scc(0)], 4); + assert_eq!(annotations.0[sccs.scc(1)], 3); + assert_eq!(annotations.0[sccs.scc(2)], 4); } + #[test] fn test_single_cycle_max() { let graph = TestGraph::new(0, &[(0, 2), (2, 3), (2, 4), (4, 1), (1, 2)]); - let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, MaxReached::from_usize); - assert_eq!(sccs.annotation(sccs.scc(2)), 4); - assert_eq!(sccs.annotation(sccs.scc(0)), 4); -} - -#[test] -fn test_simple_cycle_max() { - let graph = TestGraph::new(0, &[(0, 1), (1, 2), (2, 0)]); - let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, MaxReached::from_usize); - assert_eq!(sccs.num_sccs(), 1); + let mut annotations = Maxes(IndexVec::new(), |n| n); + let sccs = Sccs::new_with_annotation(&graph, &mut annotations); + assert_eq!(annotations.0[sccs.scc(2)], 4); + assert_eq!(annotations.0[sccs.scc(0)], 4); } #[test] fn test_double_cycle_max() { let graph = TestGraph::new(0, &[(0, 1), (1, 2), (1, 4), (2, 3), (2, 4), (3, 5), (4, 1), (5, 4)]); - let sccs: MaxReachedSccs = - Sccs::new_with_annotation(&graph, |n| if n == 5 { MaxReached(2) } else { MaxReached(1) }); + let mut annotations = Maxes(IndexVec::new(), |n| if n == 5 { 2 } else { 1 }); - assert_eq!(sccs.annotation(sccs.scc(0)).0, 2); + let sccs = Sccs::new_with_annotation(&graph, &mut annotations); + + assert_eq!(annotations.0[sccs.scc(0)].0, 2); } #[test] fn test_bug_minimised() { let graph = TestGraph::new(0, &[(0, 3), (0, 1), (3, 2), (2, 3), (1, 4), (4, 5), (5, 4)]); - let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, |n| match n { - 3 => MaxReached(1), - _ => MaxReached(0), + let mut annotations = Maxes(IndexVec::new(), |n| match n { + 3 => 1, + _ => 0, }); - assert_eq!(sccs.annotation(sccs.scc(2)), 1); - assert_eq!(sccs.annotation(sccs.scc(1)), 0); - assert_eq!(sccs.annotation(sccs.scc(4)), 0); + + let sccs = Sccs::new_with_annotation(&graph, &mut annotations); + assert_eq!(annotations.annotation(sccs.scc(2)), 1); + assert_eq!(annotations.annotation(sccs.scc(1)), 0); + assert_eq!(annotations.annotation(sccs.scc(4)), 0); } #[test] fn test_bug_max_leak_minimised() { let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (3, 0), (3, 4), (4, 3)]); - let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, |w| match w { - 4 => MaxReached(1), - _ => MaxReached(0), + let mut annotations = Maxes(IndexVec::new(), |w| match w { + 4 => 1, + _ => 0, }); - assert_eq!(sccs.annotation(sccs.scc(2)), 0); - assert_eq!(sccs.annotation(sccs.scc(3)), 1); - assert_eq!(sccs.annotation(sccs.scc(0)), 1); + let sccs = Sccs::new_with_annotation(&graph, &mut annotations); + + assert_eq!(annotations.annotation(sccs.scc(2)), 0); + assert_eq!(annotations.annotation(sccs.scc(3)), 1); + assert_eq!(annotations.annotation(sccs.scc(0)), 1); } #[test] @@ -369,48 +402,49 @@ fn test_bug_max_leak() { (23, 24), ], ); - let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, |w| match w { - 22 => MaxReached(1), - 24 => MaxReached(2), - 27 => MaxReached(2), - _ => MaxReached(0), + let mut annotations = Maxes::new(|w| match w { + 22 => 1, + 24 => 2, + 27 => 2, + _ => 0, }); - - assert_eq!(sccs.annotation(sccs.scc(2)), 0); - assert_eq!(sccs.annotation(sccs.scc(7)), 0); - assert_eq!(sccs.annotation(sccs.scc(8)), 2); - assert_eq!(sccs.annotation(sccs.scc(23)), 2); - assert_eq!(sccs.annotation(sccs.scc(3)), 2); - assert_eq!(sccs.annotation(sccs.scc(0)), 2); + let sccs = Sccs::new_with_annotation(&graph, &mut annotations); + + assert_eq!(annotations.annotation(sccs.scc(2)), 0); + assert_eq!(annotations.annotation(sccs.scc(7)), 0); + assert_eq!(annotations.annotation(sccs.scc(8)), 2); + assert_eq!(annotations.annotation(sccs.scc(23)), 2); + assert_eq!(annotations.annotation(sccs.scc(3)), 2); + assert_eq!(annotations.annotation(sccs.scc(0)), 2); } #[test] fn test_bug_max_zero_stick_shape() { let graph = TestGraph::new(0, &[(0, 1), (1, 2), (2, 3), (3, 2), (3, 4)]); - - let sccs: MaxReachedSccs = Sccs::new_with_annotation(&graph, |w| match w { - 4 => MaxReached(1), - _ => MaxReached(0), + let mut annotations = Maxes::new(|w| match w { + 4 => 1, + _ => 0, }); + let sccs = Sccs::new_with_annotation(&graph, &mut annotations); - assert_eq!(sccs.annotation(sccs.scc(0)), 1); - assert_eq!(sccs.annotation(sccs.scc(1)), 1); - assert_eq!(sccs.annotation(sccs.scc(2)), 1); - assert_eq!(sccs.annotation(sccs.scc(3)), 1); - assert_eq!(sccs.annotation(sccs.scc(4)), 1); + assert_eq!(annotations.annotation(sccs.scc(0)), 1); + assert_eq!(annotations.annotation(sccs.scc(1)), 1); + assert_eq!(annotations.annotation(sccs.scc(2)), 1); + assert_eq!(annotations.annotation(sccs.scc(3)), 1); + assert_eq!(annotations.annotation(sccs.scc(4)), 1); } #[test] fn test_min_max_in() { let graph = TestGraph::new(0, &[(0, 1), (0, 2), (1, 3), (3, 0), (3, 4), (4, 3), (3, 5)]); - let sccs: Sccs = - Sccs::new_with_annotation(&graph, |w| MinMaxIn { min: w, max: w }); - - assert_eq!(sccs.annotation(sccs.scc(2)).min, 2); - assert_eq!(sccs.annotation(sccs.scc(2)).max, 2); - assert_eq!(sccs.annotation(sccs.scc(0)).min, 0); - assert_eq!(sccs.annotation(sccs.scc(0)).max, 4); - assert_eq!(sccs.annotation(sccs.scc(3)).min, 0); - assert_eq!(sccs.annotation(sccs.scc(3)).max, 4); - assert_eq!(sccs.annotation(sccs.scc(5)).min, 5); + let mut annotations = MinMaxes(IndexVec::new(), |w| MinMaxIn { min: w, max: w }); + let sccs = Sccs::new_with_annotation(&graph, &mut annotations); + + assert_eq!(annotations.annotation(sccs.scc(2)).min, 2); + assert_eq!(annotations.annotation(sccs.scc(2)).max, 2); + assert_eq!(annotations.annotation(sccs.scc(0)).min, 0); + assert_eq!(annotations.annotation(sccs.scc(0)).max, 4); + assert_eq!(annotations.annotation(sccs.scc(3)).min, 0); + assert_eq!(annotations.annotation(sccs.scc(3)).max, 4); + assert_eq!(annotations.annotation(sccs.scc(5)).min, 5); } diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs index e3522137d838e..b2874275d15e5 100644 --- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs +++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs @@ -929,7 +929,6 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { None => false, } } - VerifyBound::OutlivedBy(r) => { let a = match min.kind() { ty::ReVar(rid) => var_values.values[rid], @@ -950,14 +949,15 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { }, _ => false, }, - VerifyBound::AnyBound(bs) => { bs.iter().any(|b| self.bound_is_met(b, var_values, generic_ty, min)) } - VerifyBound::AllBounds(bs) => { bs.iter().all(|b| self.bound_is_met(b, var_values, generic_ty, min)) } + VerifyBound::OutlivesStatic(region) => { + bug!("Saw bound `{region:?}: 'static` outside of region inference!") + } } } } diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs index 40e2e654b2ea7..bf38f2189db44 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs @@ -184,6 +184,18 @@ pub enum VerifyBound<'tcx> { /// This is used when *some* bound in `B` is known to suffice, but /// we don't know which. AllBounds(Vec>), + + /// This bound holds if a given region `R` outlives `'static`. + /// This is a rewritten bound introduced during lowering of + /// higher-ranked constraints. + OutlivesStatic(Region<'tcx>), +} + +impl<'tcx> VerifyBound<'tcx> { + /// Generate a new VerifyBound that never holds. + pub fn never_satisfied() -> Self { + VerifyBound::AnyBound(Vec::new()) + } } /// This is a "conditional bound" that checks the result of inference @@ -675,7 +687,7 @@ impl<'tcx> VerifyBound<'tcx> { pub fn must_hold(&self) -> bool { match self { VerifyBound::IfEq(..) => false, - VerifyBound::OutlivedBy(re) => re.is_static(), + VerifyBound::OutlivedBy(re) | VerifyBound::OutlivesStatic(re) => re.is_static(), VerifyBound::IsEmpty => false, VerifyBound::AnyBound(bs) => bs.iter().any(|b| b.must_hold()), VerifyBound::AllBounds(bs) => bs.iter().all(|b| b.must_hold()), @@ -689,6 +701,7 @@ impl<'tcx> VerifyBound<'tcx> { VerifyBound::OutlivedBy(_) => false, VerifyBound::AnyBound(bs) => bs.iter().all(|b| b.cannot_hold()), VerifyBound::AllBounds(bs) => bs.iter().any(|b| b.cannot_hold()), + VerifyBound::OutlivesStatic(_) => false, } } diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs index 3fc05f2caf2ad..df092b9be6fa5 100644 --- a/compiler/rustc_middle/src/mir/query.rs +++ b/compiler/rustc_middle/src/mir/query.rs @@ -12,7 +12,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisit use rustc_span::{Span, Symbol}; use super::{ConstValue, SourceInfo}; -use crate::ty::{self, CoroutineArgsExt, OpaqueHiddenType, Ty}; +use crate::ty::{self, CoroutineArgsExt, OpaqueHiddenType, RegionVid, Ty}; rustc_index::newtype_index! { #[derive(HashStable)] @@ -149,8 +149,17 @@ pub enum ConstraintCategory<'tcx> { /// A constraint that doesn't correspond to anything the user sees. Internal, - /// An internal constraint derived from an illegal universe relation. - IllegalUniverse, + /// An internal constraint derived from an illegal placeholder relation + /// to this region. The arguments are a source -> drain of a path + /// that caused the problem, used when reporting errors. + IllegalPlaceholder( + #[type_foldable(identity)] + #[type_visitable(ignore)] + RegionVid, + #[type_foldable(identity)] + #[type_visitable(ignore)] + RegionVid, + ), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs index 5056161e117e6..3acaf1f5ea736 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs @@ -59,6 +59,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { /// When given a `ConcreteFailure` for a function with arguments containing a named region and /// an anonymous region, emit a descriptive diagnostic error. pub(super) fn try_report_placeholder_conflict(&self) -> Option> { + // eprintln!("trying error: {:?}", self.error); match &self.error { /////////////////////////////////////////////////////////////////////////// // NB. The ordering of cases in this match is very @@ -195,27 +196,24 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { sup_placeholder: Option>, value_pairs: &ValuePairs<'tcx>, ) -> Option> { - let (expected_args, found_args, trait_def_id) = match value_pairs { - ValuePairs::TraitRefs(ExpectedFound { expected, found }) - if expected.def_id == found.def_id => - { - // It's possible that the placeholders come from a binder - // outside of this value pair. Use `no_bound_vars` as a - // simple heuristic for that. - (expected.args, found.args, expected.def_id) - } - _ => return None, + let ValuePairs::TraitRefs(ExpectedFound { expected, found }) = value_pairs else { + return None; }; - Some(self.report_trait_placeholder_mismatch( - vid, - cause, - sub_placeholder, - sup_placeholder, - trait_def_id, - expected_args, - found_args, - )) + // It's possible that the placeholders come from a binder + // outside of this value pair. Use `no_bound_vars` as a + // simple heuristic for that. + (expected.def_id == found.def_id).then(|| { + self.report_trait_placeholder_mismatch( + vid, + cause, + sub_placeholder, + sup_placeholder, + expected.def_id, + expected.args, + found.args, + ) + }) } // error[E0308]: implementation of `Foo` does not apply to enough lifetimes diff --git a/tests/mir-opt/nll/named_lifetimes_basic.use_x.nll.0.mir b/tests/mir-opt/nll/named_lifetimes_basic.use_x.nll.0.mir index 49f0a43295a88..5bf569ce0f9e3 100644 --- a/tests/mir-opt/nll/named_lifetimes_basic.use_x.nll.0.mir +++ b/tests/mir-opt/nll/named_lifetimes_basic.use_x.nll.0.mir @@ -8,15 +8,15 @@ | '?4 | Local | ['?4] | | Inferred Region Values -| '?0 | U0 | {bb0[0..=1], '?0, '?1, '?2, '?3, '?4} -| '?1 | U0 | {bb0[0..=1], '?1} -| '?2 | U0 | {bb0[0..=1], '?2} -| '?3 | U0 | {bb0[0..=1], '?3} -| '?4 | U0 | {bb0[0..=1], '?4} -| '?5 | U0 | {bb0[0..=1], '?1} -| '?6 | U0 | {bb0[0..=1], '?2} -| '?7 | U0 | {bb0[0..=1], '?1} -| '?8 | U0 | {bb0[0..=1], '?3} +| '?0 | {bb0[0..=1], '?0, '?1, '?2, '?3, '?4} +| '?1 | {bb0[0..=1], '?1} +| '?2 | {bb0[0..=1], '?2} +| '?3 | {bb0[0..=1], '?3} +| '?4 | {bb0[0..=1], '?4} +| '?5 | {bb0[0..=1], '?1} +| '?6 | {bb0[0..=1], '?2} +| '?7 | {bb0[0..=1], '?1} +| '?8 | {bb0[0..=1], '?3} | | Inference Constraints | '?0 live at {bb0[0..=1]} diff --git a/tests/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir b/tests/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir index 42b388033360e..85fba9ec8f841 100644 --- a/tests/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir +++ b/tests/mir-opt/nll/region_subtyping_basic.main.nll.0.32bit.mir @@ -5,11 +5,11 @@ | '?1 | Local | ['?1] | | Inferred Region Values -| '?0 | U0 | {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0], '?0, '?1} -| '?1 | U0 | {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0], '?1} -| '?2 | U0 | {bb1[0..=7], bb2[0..=2]} -| '?3 | U0 | {bb1[1..=7], bb2[0..=2]} -| '?4 | U0 | {bb1[4..=7], bb2[0..=2]} +| '?0 | {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0], '?0, '?1} +| '?1 | {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0], '?1} +| '?2 | {bb1[0..=7], bb2[0..=2]} +| '?3 | {bb1[1..=7], bb2[0..=2]} +| '?4 | {bb1[4..=7], bb2[0..=2]} | | Inference Constraints | '?0 live at {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0]} diff --git a/tests/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir b/tests/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir index 15395fd470e93..cc1386720913c 100644 --- a/tests/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir +++ b/tests/mir-opt/nll/region_subtyping_basic.main.nll.0.64bit.mir @@ -5,11 +5,11 @@ | '?1 | Local | ['?1] | | Inferred Region Values -| '?0 | U0 | {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0], '?0, '?1} -| '?1 | U0 | {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0], '?1} -| '?2 | U0 | {bb1[0..=7], bb2[0..=2]} -| '?3 | U0 | {bb1[1..=7], bb2[0..=2]} -| '?4 | U0 | {bb1[4..=7], bb2[0..=2]} +| '?0 | {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0], '?0, '?1} +| '?1 | {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0], '?1} +| '?2 | {bb1[0..=7], bb2[0..=2]} +| '?3 | {bb1[1..=7], bb2[0..=2]} +| '?4 | {bb1[4..=7], bb2[0..=2]} | | Inference Constraints | '?0 live at {bb0[0..=8], bb1[0..=7], bb2[0..=3], bb3[0..=3], bb4[0..=1], bb5[0..=2], bb6[0..=5], bb7[0]} diff --git a/tests/mir-opt/storage_ranges.main.nll.0.mir b/tests/mir-opt/storage_ranges.main.nll.0.mir index ae8cd0c894dac..f1c6a1782f36d 100644 --- a/tests/mir-opt/storage_ranges.main.nll.0.mir +++ b/tests/mir-opt/storage_ranges.main.nll.0.mir @@ -5,10 +5,10 @@ | '?1 | Local | ['?1] | | Inferred Region Values -| '?0 | U0 | {bb0[0..=22], '?0, '?1} -| '?1 | U0 | {bb0[0..=22], '?1} -| '?2 | U0 | {bb0[10..=11]} -| '?3 | U0 | {bb0[11]} +| '?0 | {bb0[0..=22], '?0, '?1} +| '?1 | {bb0[0..=22], '?1} +| '?2 | {bb0[10..=11]} +| '?3 | {bb0[11]} | | Inference Constraints | '?0 live at {bb0[0..=22]} diff --git a/tests/ui/borrowck/static-trait-bound-lost-closure.rs b/tests/ui/borrowck/static-trait-bound-lost-closure.rs new file mode 100644 index 0000000000000..109383d65d691 --- /dev/null +++ b/tests/ui/borrowck/static-trait-bound-lost-closure.rs @@ -0,0 +1,54 @@ +// This test is a reduced version of a bug introduced during work on type-tests for Polonius. +// The underlying problem is that the 'static bound is lost for a closure that is threaded +// deeply enough, causing an error. +// The bug was first observed in exr-1.4.1/src/image/read/mod.rs:124:5 during perf test. + +//@ check-pass + +use std::marker::PhantomData; + +struct ReadAllLayers { + px: PhantomData, +} + +trait ReadLayers<'s> {} + +impl<'s, C> ReadLayers<'s> for ReadAllLayers where C: ReadChannels<'s> {} + +fn make_builder( + _: Set, +) -> ReadAllLayers> +where + Set: Fn(&mut Pixels), +{ + todo!() +} + +struct CollectPixels { + px: PhantomData<(SetPixel, Pixel, PixelStorage)>, +} + +impl<'s, PixelStorage, SetPixel: 's> ReadChannels<'s> + for CollectPixels +where + SetPixel: Fn(&mut PixelStorage), +{ +} + +trait ReadChannels<'s> {} + +fn from_file(_: L) +where + for<'s> L: ReadLayers<'s>, +{ +} + +pub fn read_all_rgba_layers_from_file( + set_pixel: Set, +) where + Set: Fn(&mut Pixels), +{ + from_file(make_builder(set_pixel)); // Error triggered. +} + +pub fn main() {} diff --git a/tests/ui/coroutine/auto-trait-regions.rs b/tests/ui/coroutine/auto-trait-regions.rs index 1c7f0304ddbec..fb448e80b99e4 100644 --- a/tests/ui/coroutine/auto-trait-regions.rs +++ b/tests/ui/coroutine/auto-trait-regions.rs @@ -20,6 +20,19 @@ impl<'a> Foo for &'a OnlyFooIfRef {} fn assert_foo(f: T) {} +fn other_assertion() { + // Disallow impls which relates lifetimes in the coroutine interior + let gen = #[coroutine] move || { + let a = A(&mut true, &mut true, No); + //~^ temporary value dropped while borrowed + //~| temporary value dropped while borrowed + yield; + assert_foo(a); + }; + assert_foo(gen); + //~^ ERROR not general enough +} + fn main() { // Make sure 'static is erased for coroutine interiors so we can't match it in trait selection let x: &'static _ = &OnlyFooIfStaticRef(No); @@ -39,15 +52,4 @@ fn main() { assert_foo(x); }; assert_foo(gen); // ok - - // Disallow impls which relates lifetimes in the coroutine interior - let gen = #[coroutine] move || { - let a = A(&mut true, &mut true, No); - //~^ ERROR temporary value dropped while borrowed - //~| ERROR temporary value dropped while borrowed - yield; - assert_foo(a); - }; - assert_foo(gen); - //~^ ERROR not general enough } diff --git a/tests/ui/coroutine/auto-trait-regions.stderr b/tests/ui/coroutine/auto-trait-regions.stderr index a9a0bde2ba019..81cb2e93a4939 100644 --- a/tests/ui/coroutine/auto-trait-regions.stderr +++ b/tests/ui/coroutine/auto-trait-regions.stderr @@ -1,5 +1,5 @@ error[E0716]: temporary value dropped while borrowed - --> $DIR/auto-trait-regions.rs:45:24 + --> $DIR/auto-trait-regions.rs:26:24 | LL | let a = A(&mut true, &mut true, No); | ^^^^ - temporary value is freed at the end of this statement @@ -16,7 +16,7 @@ LL ~ let a = A(&mut binding, &mut true, No); | error[E0716]: temporary value dropped while borrowed - --> $DIR/auto-trait-regions.rs:45:35 + --> $DIR/auto-trait-regions.rs:26:35 | LL | let a = A(&mut true, &mut true, No); | ^^^^ - temporary value is freed at the end of this statement @@ -33,22 +33,22 @@ LL ~ let a = A(&mut true, &mut binding, No); | error: implementation of `Foo` is not general enough - --> $DIR/auto-trait-regions.rs:31:5 + --> $DIR/auto-trait-regions.rs:32:5 | LL | assert_foo(gen); | ^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough | - = note: `&'0 OnlyFooIfStaticRef` must implement `Foo`, for any lifetime `'0`... - = note: ...but `Foo` is actually implemented for the type `&'static OnlyFooIfStaticRef` + = note: `Foo` would have to be implemented for the type `A<'0, '1>`, for any two lifetimes `'0` and `'1`... + = note: ...but `Foo` is actually implemented for the type `A<'_, '2>`, for some specific lifetime `'2` error: implementation of `Foo` is not general enough - --> $DIR/auto-trait-regions.rs:51:5 + --> $DIR/auto-trait-regions.rs:44:5 | LL | assert_foo(gen); | ^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough | - = note: `Foo` would have to be implemented for the type `A<'0, '1>`, for any two lifetimes `'0` and `'1`... - = note: ...but `Foo` is actually implemented for the type `A<'_, '2>`, for some specific lifetime `'2` + = note: `&'0 OnlyFooIfStaticRef` must implement `Foo`, for any lifetime `'0`... + = note: ...but `Foo` is actually implemented for the type `&'static OnlyFooIfStaticRef` error: aborting due to 4 previous errors diff --git a/tests/ui/generic-associated-types/bugs/issue-100013.stderr b/tests/ui/generic-associated-types/bugs/issue-100013.stderr index ff82aebfef911..e2214c56b4cf7 100644 --- a/tests/ui/generic-associated-types/bugs/issue-100013.stderr +++ b/tests/ui/generic-associated-types/bugs/issue-100013.stderr @@ -9,17 +9,6 @@ LL | | } | = note: this is a known limitation that will be removed in the future (see issue #100013 for more information) -error: lifetime bound not satisfied - --> $DIR/issue-100013.rs:22:5 - | -LL | / async { // a coroutine checked for autotrait impl `Send` -LL | | let x = None::>; // a type referencing GAT -LL | | async {}.await; // a yield point -LL | | } - | |_____^ - | - = note: this is a known limitation that will be removed in the future (see issue #100013 for more information) - error: lifetime may not live long enough --> $DIR/issue-100013.rs:23:17 | @@ -44,5 +33,5 @@ LL | | } | = note: this is a known limitation that will be removed in the future (see issue #100013 for more information) -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/higher-ranked/trait-bounds/due-to-where-clause.stderr b/tests/ui/higher-ranked/trait-bounds/due-to-where-clause.stderr index 916854e07afbe..6c17b1f4874b2 100644 --- a/tests/ui/higher-ranked/trait-bounds/due-to-where-clause.stderr +++ b/tests/ui/higher-ranked/trait-bounds/due-to-where-clause.stderr @@ -2,7 +2,7 @@ error: implementation of `Foo` is not general enough --> $DIR/due-to-where-clause.rs:2:5 | LL | test::(&mut 42); - | ^^^^^^^^^^^^ implementation of `Foo` is not general enough + | ^^^^^^^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough | = note: `FooS<'_>` must implement `Foo<'0>`, for any lifetime `'0`... = note: ...but `FooS<'_>` actually implements `Foo<'1>`, for some specific lifetime `'1` diff --git a/tests/ui/nll/ice-106874.stderr b/tests/ui/nll/ice-106874.stderr index ead4d490a6248..5bf311932d095 100644 --- a/tests/ui/nll/ice-106874.stderr +++ b/tests/ui/nll/ice-106874.stderr @@ -17,6 +17,15 @@ LL | A(B(C::new(D::new(move |st| f(st))))) = note: ...but it actually implements `FnOnce<(&'1 mut V,)>`, for some specific lifetime `'1` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +error: implementation of `FnOnce` is not general enough + --> $DIR/ice-106874.rs:8:7 + | +LL | A(B(C::new(D::new(move |st| f(st))))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough + | + = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... + = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` + error: implementation of `Fn` is not general enough --> $DIR/ice-106874.rs:8:7 | @@ -34,6 +43,7 @@ LL | A(B(C::new(D::new(move |st| f(st))))) | = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: implementation of `Fn` is not general enough --> $DIR/ice-106874.rs:8:7 @@ -46,43 +56,36 @@ LL | A(B(C::new(D::new(move |st| f(st))))) = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: implementation of `FnOnce` is not general enough - --> $DIR/ice-106874.rs:8:9 + --> $DIR/ice-106874.rs:8:7 | LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^ implementation of `FnOnce` is not general enough + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough | = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: implementation of `Fn` is not general enough - --> $DIR/ice-106874.rs:8:9 + --> $DIR/ice-106874.rs:8:7 | LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough | = note: closure with signature `fn(&'2 mut V)` must implement `Fn<(&'1 mut V,)>`, for any lifetime `'1`... = note: ...but it actually implements `Fn<(&'2 mut V,)>`, for some specific lifetime `'2` - -error: implementation of `FnOnce` is not general enough - --> $DIR/ice-106874.rs:8:9 - | -LL | A(B(C::new(D::new(move |st| f(st))))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough - | - = note: closure with signature `fn(&'2 mut V)` must implement `FnOnce<(&'1 mut V,)>`, for any lifetime `'1`... - = note: ...but it actually implements `FnOnce<(&'2 mut V,)>`, for some specific lifetime `'2` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: higher-ranked subtype error - --> $DIR/ice-106874.rs:8:41 + --> $DIR/ice-106874.rs:8:7 | LL | A(B(C::new(D::new(move |st| f(st))))) - | ^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: higher-ranked subtype error - --> $DIR/ice-106874.rs:8:41 + --> $DIR/ice-106874.rs:8:7 | LL | A(B(C::new(D::new(move |st| f(st))))) - | ^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` diff --git a/tests/ui/nll/missing-universe-cause-issue-114907.stderr b/tests/ui/nll/missing-universe-cause-issue-114907.stderr index 26ad1efec0558..eeafa6f0616ce 100644 --- a/tests/ui/nll/missing-universe-cause-issue-114907.stderr +++ b/tests/ui/nll/missing-universe-cause-issue-114907.stderr @@ -38,16 +38,16 @@ LL | accept(callback); = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: higher-ranked subtype error - --> $DIR/missing-universe-cause-issue-114907.rs:33:21 + --> $DIR/missing-universe-cause-issue-114907.rs:33:5 | LL | accept(callback); - | ^ + | ^^^^^^^^^^^^^^^^ error: higher-ranked subtype error - --> $DIR/missing-universe-cause-issue-114907.rs:33:21 + --> $DIR/missing-universe-cause-issue-114907.rs:33:5 | LL | accept(callback); - | ^ + | ^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`