diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs index c909a0b49e24e..73e94da165fb0 100644 --- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs +++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs @@ -6,13 +6,13 @@ use super::*; use crate::errors::UnableToConstructConstantValue; use crate::infer::region_constraints::{Constraint, RegionConstraintData}; use crate::traits::project::ProjectAndUnifyResult; + +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry}; +use rustc_data_structures::unord::UnordSet; use rustc_infer::infer::DefineOpaqueTypes; use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::ty::{Region, RegionVid}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; - -use std::collections::hash_map::Entry; use std::collections::VecDeque; use std::iter; @@ -25,8 +25,8 @@ pub enum RegionTarget<'tcx> { #[derive(Default, Debug, Clone)] pub struct RegionDeps<'tcx> { - larger: FxIndexSet>, - smaller: FxIndexSet>, + pub larger: FxIndexSet>, + pub smaller: FxIndexSet>, } pub enum AutoTraitResult { @@ -35,17 +35,10 @@ pub enum AutoTraitResult { NegativeImpl, } -#[allow(dead_code)] -impl AutoTraitResult { - fn is_auto(&self) -> bool { - matches!(self, AutoTraitResult::PositiveImpl(_) | AutoTraitResult::NegativeImpl) - } -} - pub struct AutoTraitInfo<'cx> { pub full_user_env: ty::ParamEnv<'cx>, pub region_data: RegionConstraintData<'cx>, - pub vid_to_region: FxHashMap>, + pub vid_to_region: FxIndexMap>, } pub struct AutoTraitFinder<'tcx> { @@ -88,19 +81,12 @@ impl<'tcx> AutoTraitFinder<'tcx> { let infcx = tcx.infer_ctxt().build(); let mut selcx = SelectionContext::new(&infcx); - for polarity in [true, false] { + for polarity in [ty::PredicatePolarity::Positive, ty::PredicatePolarity::Negative] { let result = selcx.select(&Obligation::new( tcx, ObligationCause::dummy(), orig_env, - ty::TraitPredicate { - trait_ref, - polarity: if polarity { - ty::PredicatePolarity::Positive - } else { - ty::PredicatePolarity::Negative - }, - }, + ty::TraitPredicate { trait_ref, polarity }, )); if let Ok(Some(ImplSource::UserDefined(_))) = result { debug!( @@ -114,7 +100,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { } let infcx = tcx.infer_ctxt().build(); - let mut fresh_preds = FxHashSet::default(); + let mut fresh_preds = FxIndexSet::default(); // Due to the way projections are handled by SelectionContext, we need to run // evaluate_predicates twice: once on the original param env, and once on the result of @@ -239,7 +225,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { ty: Ty<'tcx>, param_env: ty::ParamEnv<'tcx>, user_env: ty::ParamEnv<'tcx>, - fresh_preds: &mut FxHashSet>, + fresh_preds: &mut FxIndexSet>, ) -> Option<(ty::ParamEnv<'tcx>, ty::ParamEnv<'tcx>)> { let tcx = infcx.tcx; @@ -252,7 +238,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { let mut select = SelectionContext::new(infcx); - let mut already_visited = FxHashSet::default(); + let mut already_visited = UnordSet::new(); let mut predicates = VecDeque::new(); predicates.push_back(ty::Binder::dummy(ty::TraitPredicate { trait_ref: ty::TraitRef::new(infcx.tcx, trait_did, [ty]), @@ -473,9 +459,9 @@ impl<'tcx> AutoTraitFinder<'tcx> { fn map_vid_to_region<'cx>( &self, regions: &RegionConstraintData<'cx>, - ) -> FxHashMap> { - let mut vid_map: FxHashMap, RegionDeps<'cx>> = FxHashMap::default(); - let mut finished_map = FxHashMap::default(); + ) -> FxIndexMap> { + let mut vid_map = FxIndexMap::, RegionDeps<'cx>>::default(); + let mut finished_map = FxIndexMap::default(); for (constraint, _) in ®ions.constraints { match constraint { @@ -513,25 +499,22 @@ impl<'tcx> AutoTraitFinder<'tcx> { } while !vid_map.is_empty() { - #[allow(rustc::potential_query_instability)] - let target = *vid_map.keys().next().expect("Keys somehow empty"); - let deps = vid_map.remove(&target).expect("Entry somehow missing"); + let target = *vid_map.keys().next().unwrap(); + let deps = vid_map.swap_remove(&target).unwrap(); for smaller in deps.smaller.iter() { for larger in deps.larger.iter() { match (smaller, larger) { (&RegionTarget::Region(_), &RegionTarget::Region(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*smaller) { + if let IndexEntry::Occupied(v) = vid_map.entry(*smaller) { let smaller_deps = v.into_mut(); smaller_deps.larger.insert(*larger); - // FIXME(#120456) - is `swap_remove` correct? smaller_deps.larger.swap_remove(&target); } - if let Entry::Occupied(v) = vid_map.entry(*larger) { + if let IndexEntry::Occupied(v) = vid_map.entry(*larger) { let larger_deps = v.into_mut(); larger_deps.smaller.insert(*smaller); - // FIXME(#120456) - is `swap_remove` correct? larger_deps.smaller.swap_remove(&target); } } @@ -542,17 +525,15 @@ impl<'tcx> AutoTraitFinder<'tcx> { // Do nothing; we don't care about regions that are smaller than vids. } (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*smaller) { + if let IndexEntry::Occupied(v) = vid_map.entry(*smaller) { let smaller_deps = v.into_mut(); smaller_deps.larger.insert(*larger); - // FIXME(#120456) - is `swap_remove` correct? smaller_deps.larger.swap_remove(&target); } - if let Entry::Occupied(v) = vid_map.entry(*larger) { + if let IndexEntry::Occupied(v) = vid_map.entry(*larger) { let larger_deps = v.into_mut(); larger_deps.smaller.insert(*smaller); - // FIXME(#120456) - is `swap_remove` correct? larger_deps.smaller.swap_remove(&target); } } @@ -560,6 +541,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { } } } + finished_map } @@ -588,7 +570,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { ty: Ty<'_>, nested: impl Iterator>, computed_preds: &mut FxIndexSet>, - fresh_preds: &mut FxHashSet>, + fresh_preds: &mut FxIndexSet>, predicates: &mut VecDeque>, selcx: &mut SelectionContext<'_, 'tcx>, ) -> bool { diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index fbc2c3c5af459..d6659015385b5 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -1,747 +1,357 @@ +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry}; use rustc_hir as hir; -use rustc_hir::lang_items::LangItem; -use rustc_middle::ty::{Region, RegionVid, TypeFoldable}; -use rustc_trait_selection::traits::auto_trait::{self, AutoTraitResult}; - -use std::fmt::Debug; - -use super::*; - -#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] -enum RegionTarget<'tcx> { - Region(Region<'tcx>), - RegionVid(RegionVid), -} - -#[derive(Default, Debug, Clone)] -struct RegionDeps<'tcx> { - larger: FxHashSet>, - smaller: FxHashSet>, -} - -pub(crate) struct AutoTraitFinder<'a, 'tcx> { - pub(crate) cx: &'a mut core::DocContext<'tcx>, +use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData}; +use rustc_middle::ty::{self, Region, Ty}; +use rustc_span::def_id::DefId; +use rustc_span::symbol::{kw, Symbol}; +use rustc_trait_selection::traits::auto_trait::{self, RegionTarget}; + +use thin_vec::ThinVec; + +use crate::clean::{self, simplify, Lifetime}; +use crate::clean::{ + clean_generic_param_def, clean_middle_ty, clean_predicate, clean_trait_ref_with_bindings, + clean_ty_generics, +}; +use crate::core::DocContext; + +#[instrument(level = "debug", skip(cx))] +pub(crate) fn synthesize_auto_trait_impls<'tcx>( + cx: &mut DocContext<'tcx>, + item_def_id: DefId, +) -> Vec { + let tcx = cx.tcx; + let param_env = tcx.param_env(item_def_id); + let ty = tcx.type_of(item_def_id).instantiate_identity(); + + let finder = auto_trait::AutoTraitFinder::new(tcx); + let mut auto_trait_impls: Vec<_> = cx + .auto_traits + .clone() + .into_iter() + .filter_map(|trait_def_id| { + synthesize_auto_trait_impl( + cx, + ty, + trait_def_id, + param_env, + item_def_id, + &finder, + DiscardPositiveImpls::No, + ) + }) + .collect(); + // We are only interested in case the type *doesn't* implement the `Sized` trait. + if !ty.is_sized(tcx, param_env) + && let Some(sized_trait_def_id) = tcx.lang_items().sized_trait() + && let Some(impl_item) = synthesize_auto_trait_impl( + cx, + ty, + sized_trait_def_id, + param_env, + item_def_id, + &finder, + DiscardPositiveImpls::Yes, + ) + { + auto_trait_impls.push(impl_item); + } + auto_trait_impls } -impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> -where - 'tcx: 'a, // should be an implied bound; rustc bug #98852. -{ - pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> Self { - AutoTraitFinder { cx } +#[instrument(level = "debug", skip(cx, finder))] +fn synthesize_auto_trait_impl<'tcx>( + cx: &mut DocContext<'tcx>, + ty: Ty<'tcx>, + trait_def_id: DefId, + param_env: ty::ParamEnv<'tcx>, + item_def_id: DefId, + finder: &auto_trait::AutoTraitFinder<'tcx>, + discard_positive_impls: DiscardPositiveImpls, +) -> Option { + let tcx = cx.tcx; + let trait_ref = ty::Binder::dummy(ty::TraitRef::new(tcx, trait_def_id, [ty])); + if !cx.generated_synthetics.insert((ty, trait_def_id)) { + debug!("already generated, aborting"); + return None; } - fn generate_for_trait( - &mut self, - ty: Ty<'tcx>, - trait_def_id: DefId, - param_env: ty::ParamEnv<'tcx>, - item_def_id: DefId, - f: &auto_trait::AutoTraitFinder<'tcx>, - // If this is set, show only negative trait implementations, not positive ones. - discard_positive_impl: bool, - ) -> Option { - let tcx = self.cx.tcx; - let trait_ref = ty::Binder::dummy(ty::TraitRef::new(tcx, trait_def_id, [ty])); - if !self.cx.generated_synthetics.insert((ty, trait_def_id)) { - debug!("get_auto_trait_impl_for({trait_ref:?}): already generated, aborting"); - return None; - } + let result = finder.find_auto_trait_generics(ty, param_env, trait_def_id, |info| { + clean_param_env(cx, item_def_id, info.full_user_env, info.region_data, info.vid_to_region) + }); - let result = f.find_auto_trait_generics(ty, param_env, trait_def_id, |info| { - let region_data = info.region_data; + let (generics, polarity) = match result { + auto_trait::AutoTraitResult::PositiveImpl(generics) => { + if let DiscardPositiveImpls::Yes = discard_positive_impls { + return None; + } - let names_map = tcx - .generics_of(item_def_id) - .params - .iter() - .filter_map(|param| match param.kind { - ty::GenericParamDefKind::Lifetime => Some(param.name), - _ => None, - }) - .map(|name| (name, Lifetime(name))) - .collect(); - let lifetime_predicates = Self::handle_lifetimes(®ion_data, &names_map); - let new_generics = self.param_env_to_generics( - item_def_id, - info.full_user_env, - lifetime_predicates, - info.vid_to_region, + (generics, ty::ImplPolarity::Positive) + } + auto_trait::AutoTraitResult::NegativeImpl => { + // For negative impls, we use the generic params, but *not* the predicates, + // from the original type. Otherwise, the displayed impl appears to be a + // conditional negative impl, when it's really unconditional. + // + // For example, consider the struct Foo(*mut T). Using + // the original predicates in our impl would cause us to generate + // `impl !Send for Foo`, which makes it appear that Foo + // implements Send where T is not copy. + // + // Instead, we generate `impl !Send for Foo`, which better + // expresses the fact that `Foo` never implements `Send`, + // regardless of the choice of `T`. + let mut generics = clean_ty_generics( + cx, + tcx.generics_of(item_def_id), + ty::GenericPredicates::default(), ); + generics.where_predicates.clear(); - debug!( - "find_auto_trait_generics(item_def_id={:?}, trait_def_id={:?}): \ - finished with {:?}", - item_def_id, trait_def_id, new_generics - ); + (generics, ty::ImplPolarity::Negative) + } + auto_trait::AutoTraitResult::ExplicitImpl => return None, + }; + + Some(clean::Item { + name: None, + attrs: Default::default(), + item_id: clean::ItemId::Auto { trait_: trait_def_id, for_: item_def_id }, + kind: Box::new(clean::ImplItem(Box::new(clean::Impl { + unsafety: hir::Unsafety::Normal, + generics, + trait_: Some(clean_trait_ref_with_bindings(cx, trait_ref, ThinVec::new())), + for_: clean_middle_ty(ty::Binder::dummy(ty), cx, None, None), + items: Vec::new(), + polarity, + kind: clean::ImplKind::Auto, + }))), + cfg: None, + inline_stmt_id: None, + }) +} - new_generics - }); +#[derive(Debug)] +enum DiscardPositiveImpls { + Yes, + No, +} - let polarity; - let new_generics = match result { - AutoTraitResult::PositiveImpl(new_generics) => { - polarity = ty::ImplPolarity::Positive; - if discard_positive_impl { - return None; +#[instrument(level = "debug", skip(cx, region_data, vid_to_region))] +fn clean_param_env<'tcx>( + cx: &mut DocContext<'tcx>, + item_def_id: DefId, + param_env: ty::ParamEnv<'tcx>, + region_data: RegionConstraintData<'tcx>, + vid_to_region: FxIndexMap>, +) -> clean::Generics { + let tcx = cx.tcx; + let generics = tcx.generics_of(item_def_id); + + let params: ThinVec<_> = generics + .params + .iter() + .inspect(|param| { + if cfg!(debug_assertions) { + debug_assert!(!param.is_anonymous_lifetime() && !param.is_host_effect()); + if let ty::GenericParamDefKind::Type { synthetic, .. } = param.kind { + debug_assert!(!synthetic && param.name != kw::SelfUpper); } - new_generics } - AutoTraitResult::NegativeImpl => { - polarity = ty::ImplPolarity::Negative; - - // For negative impls, we use the generic params, but *not* the predicates, - // from the original type. Otherwise, the displayed impl appears to be a - // conditional negative impl, when it's really unconditional. - // - // For example, consider the struct Foo(*mut T). Using - // the original predicates in our impl would cause us to generate - // `impl !Send for Foo`, which makes it appear that Foo - // implements Send where T is not copy. - // - // Instead, we generate `impl !Send for Foo`, which better - // expresses the fact that `Foo` never implements `Send`, - // regardless of the choice of `T`. - let raw_generics = clean_ty_generics( - self.cx, - tcx.generics_of(item_def_id), - ty::GenericPredicates::default(), - ); - let params = raw_generics.params; - - Generics { params, where_predicates: ThinVec::new() } - } - AutoTraitResult::ExplicitImpl => return None, - }; - - Some(Item { - name: None, - attrs: Default::default(), - item_id: ItemId::Auto { trait_: trait_def_id, for_: item_def_id }, - kind: Box::new(ImplItem(Box::new(Impl { - unsafety: hir::Unsafety::Normal, - generics: new_generics, - trait_: Some(clean_trait_ref_with_bindings(self.cx, trait_ref, ThinVec::new())), - for_: clean_middle_ty(ty::Binder::dummy(ty), self.cx, None, None), - items: Vec::new(), - polarity, - kind: ImplKind::Auto, - }))), - cfg: None, - inline_stmt_id: None, }) - } - - pub(crate) fn get_auto_trait_impls(&mut self, item_def_id: DefId) -> Vec { - let tcx = self.cx.tcx; - let param_env = tcx.param_env(item_def_id); - let ty = tcx.type_of(item_def_id).instantiate_identity(); - let f = auto_trait::AutoTraitFinder::new(tcx); - - debug!("get_auto_trait_impls({ty:?})"); - let auto_traits: Vec<_> = self.cx.auto_traits.to_vec(); - let mut auto_traits: Vec = auto_traits - .into_iter() - .filter_map(|trait_def_id| { - self.generate_for_trait(ty, trait_def_id, param_env, item_def_id, &f, false) - }) - .collect(); - // We are only interested in case the type *doesn't* implement the Sized trait. - if !ty.is_sized(tcx, param_env) { - // In case `#![no_core]` is used, `sized_trait` returns nothing. - if let Some(item) = tcx.lang_items().sized_trait().and_then(|sized_trait_did| { - self.generate_for_trait(ty, sized_trait_did, param_env, item_def_id, &f, true) - }) { - auto_traits.push(item); - } - } - auto_traits - } - - fn get_lifetime(region: Region<'_>, names_map: &FxHashMap) -> Lifetime { - region_name(region) - .map(|name| { - names_map - .get(&name) - .unwrap_or_else(|| panic!("Missing lifetime with name {name:?} for {region:?}")) + // We're basing the generics of the synthetic auto trait impl off of the generics of the + // implementing type. Its generic parameters may have defaults, don't copy them over: + // Generic parameter defaults are meaningless in impls. + .map(|param| clean_generic_param_def(param, clean::ParamDefaults::No, cx)) + .collect(); + + // FIXME(#111101): Incorporate the explicit predicates of the item here... + let item_predicates: FxIndexSet<_> = + tcx.predicates_of(item_def_id).predicates.iter().map(|(pred, _)| pred).collect(); + let where_predicates = param_env + .caller_bounds() + .iter() + // FIXME: ...which hopefully allows us to simplify this: + .filter(|pred| { + !item_predicates.contains(pred) + || pred + .as_trait_clause() + .is_some_and(|pred| tcx.lang_items().sized_trait() == Some(pred.def_id())) + }) + .map(|pred| { + tcx.fold_regions(pred, |r, _| match *r { + ty::ReVar(vid) => vid_to_region[&vid], + _ => r, }) - .unwrap_or(&Lifetime::statik()) - .clone() - } - - /// This method calculates two things: Lifetime constraints of the form `'a: 'b`, - /// and region constraints of the form `RegionVid: 'a` - /// - /// This is essentially a simplified version of lexical_region_resolve. However, - /// handle_lifetimes determines what *needs be* true in order for an impl to hold. - /// lexical_region_resolve, along with much of the rest of the compiler, is concerned - /// with determining if a given set up constraints/predicates *are* met, given some - /// starting conditions (e.g., user-provided code). For this reason, it's easier - /// to perform the calculations we need on our own, rather than trying to make - /// existing inference/solver code do what we want. - fn handle_lifetimes<'cx>( - regions: &RegionConstraintData<'cx>, - names_map: &FxHashMap, - ) -> ThinVec { - // Our goal is to 'flatten' the list of constraints by eliminating - // all intermediate RegionVids. At the end, all constraints should - // be between Regions (aka region variables). This gives us the information - // we need to create the Generics. - let mut finished: FxHashMap<_, Vec<_>> = Default::default(); - - let mut vid_map: FxHashMap, RegionDeps<'_>> = Default::default(); - - // Flattening is done in two parts. First, we insert all of the constraints - // into a map. Each RegionTarget (either a RegionVid or a Region) maps - // to its smaller and larger regions. Note that 'larger' regions correspond - // to sub-regions in Rust code (e.g., in 'a: 'b, 'a is the larger region). - for (constraint, _) in ®ions.constraints { - match *constraint { - Constraint::VarSubVar(r1, r2) => { - { - let deps1 = vid_map.entry(RegionTarget::RegionVid(r1)).or_default(); - deps1.larger.insert(RegionTarget::RegionVid(r2)); - } + }) + .flat_map(|pred| clean_predicate(pred, cx)) + .chain(clean_region_outlives_constraints(®ion_data, generics)) + .collect(); + + let mut generics = clean::Generics { params, where_predicates }; + simplify::sized_bounds(cx, &mut generics); + generics.where_predicates = simplify::where_clauses(cx, generics.where_predicates); + generics +} - let deps2 = vid_map.entry(RegionTarget::RegionVid(r2)).or_default(); - deps2.smaller.insert(RegionTarget::RegionVid(r1)); - } - Constraint::RegSubVar(region, vid) => { - let deps = vid_map.entry(RegionTarget::RegionVid(vid)).or_default(); - deps.smaller.insert(RegionTarget::Region(region)); - } - Constraint::VarSubReg(vid, region) => { - let deps = vid_map.entry(RegionTarget::RegionVid(vid)).or_default(); - deps.larger.insert(RegionTarget::Region(region)); - } - Constraint::RegSubReg(r1, r2) => { - // The constraint is already in the form that we want, so we're done with it - // Desired order is 'larger, smaller', so flip then - if region_name(r1) != region_name(r2) { - finished - .entry(region_name(r2).expect("no region_name found")) - .or_default() - .push(r1); - } - } +/// Clean region outlives constraints to where-predicates. +/// +/// This is essentially a simplified version of `lexical_region_resolve`. +/// +/// However, here we determine what *needs to be* true in order for an impl to hold. +/// `lexical_region_resolve`, along with much of the rest of the compiler, is concerned +/// with determining if a given set up constraints / predicates *are* met, given some +/// starting conditions like user-provided code. +/// +/// For this reason, it's easier to perform the calculations we need on our own, +/// rather than trying to make existing inference/solver code do what we want. +fn clean_region_outlives_constraints<'tcx>( + regions: &RegionConstraintData<'tcx>, + generics: &'tcx ty::Generics, +) -> ThinVec { + // Our goal is to "flatten" the list of constraints by eliminating all intermediate + // `RegionVids` (region inference variables). At the end, all constraints should be + // between `Region`s. This gives us the information we need to create the where-predicates. + // This flattening is done in two parts. + + let mut outlives_predicates = FxIndexMap::<_, Vec<_>>::default(); + let mut map = FxIndexMap::, auto_trait::RegionDeps<'_>>::default(); + + // (1) We insert all of the constraints into a map. + // Each `RegionTarget` (a `RegionVid` or a `Region`) maps to its smaller and larger regions. + // Note that "larger" regions correspond to sub regions in the surface language. + // E.g., in `'a: 'b`, `'a` is the larger region. + for (constraint, _) in ®ions.constraints { + match *constraint { + Constraint::VarSubVar(vid1, vid2) => { + let deps1 = map.entry(RegionTarget::RegionVid(vid1)).or_default(); + deps1.larger.insert(RegionTarget::RegionVid(vid2)); + + let deps2 = map.entry(RegionTarget::RegionVid(vid2)).or_default(); + deps2.smaller.insert(RegionTarget::RegionVid(vid1)); } - } - - // Here, we 'flatten' the map one element at a time. - // All of the element's sub and super regions are connected - // to each other. For example, if we have a graph that looks like this: - // - // (A, B) - C - (D, E) - // Where (A, B) are subregions, and (D,E) are super-regions - // - // then after deleting 'C', the graph will look like this: - // ... - A - (D, E ...) - // ... - B - (D, E, ...) - // (A, B, ...) - D - ... - // (A, B, ...) - E - ... - // - // where '...' signifies the existing sub and super regions of an entry - // When two adjacent ty::Regions are encountered, we've computed a final - // constraint, and add it to our list. Since we make sure to never re-add - // deleted items, this process will always finish. - while !vid_map.is_empty() { - let target = *vid_map.keys().next().expect("Keys somehow empty"); - let deps = vid_map.remove(&target).expect("Entry somehow missing"); - - for smaller in deps.smaller.iter() { - for larger in deps.larger.iter() { - match (smaller, larger) { - (&RegionTarget::Region(r1), &RegionTarget::Region(r2)) => { - if region_name(r1) != region_name(r2) { - finished - .entry(region_name(r2).expect("no region name found")) - .or_default() - .push(r1) // Larger, smaller - } - } - (&RegionTarget::RegionVid(_), &RegionTarget::Region(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*smaller) { - let smaller_deps = v.into_mut(); - smaller_deps.larger.insert(*larger); - smaller_deps.larger.remove(&target); - } - } - (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*larger) { - let deps = v.into_mut(); - deps.smaller.insert(*smaller); - deps.smaller.remove(&target); - } - } - (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*smaller) { - let smaller_deps = v.into_mut(); - smaller_deps.larger.insert(*larger); - smaller_deps.larger.remove(&target); - } - - if let Entry::Occupied(v) = vid_map.entry(*larger) { - let larger_deps = v.into_mut(); - larger_deps.smaller.insert(*smaller); - larger_deps.smaller.remove(&target); - } - } - } + Constraint::RegSubVar(region, vid) => { + let deps = map.entry(RegionTarget::RegionVid(vid)).or_default(); + deps.smaller.insert(RegionTarget::Region(region)); + } + Constraint::VarSubReg(vid, region) => { + let deps = map.entry(RegionTarget::RegionVid(vid)).or_default(); + deps.larger.insert(RegionTarget::Region(region)); + } + Constraint::RegSubReg(r1, r2) => { + // The constraint is already in the form that we want, so we're done with it + // The desired order is [larger, smaller], so flip them. + if early_bound_region_name(r1) != early_bound_region_name(r2) { + outlives_predicates + .entry(early_bound_region_name(r2).expect("no region_name found")) + .or_default() + .push(r1); } } } - - let lifetime_predicates = names_map - .iter() - .flat_map(|(name, lifetime)| { - let empty = Vec::new(); - let bounds: FxHashSet = finished - .get(name) - .unwrap_or(&empty) - .iter() - .map(|region| GenericBound::Outlives(Self::get_lifetime(*region, names_map))) - .collect(); - - if bounds.is_empty() { - return None; - } - Some(WherePredicate::RegionPredicate { - lifetime: lifetime.clone(), - bounds: bounds.into_iter().collect(), - }) - }) - .collect(); - - lifetime_predicates } - fn extract_for_generics(&self, pred: ty::Clause<'tcx>) -> FxHashSet { - let bound_predicate = pred.kind(); - let tcx = self.cx.tcx; - let regions = - match bound_predicate.skip_binder() { - ty::ClauseKind::Trait(poly_trait_pred) => tcx - .collect_referenced_late_bound_regions(bound_predicate.rebind(poly_trait_pred)), - ty::ClauseKind::Projection(poly_proj_pred) => tcx - .collect_referenced_late_bound_regions(bound_predicate.rebind(poly_proj_pred)), - _ => return FxHashSet::default(), - }; - - regions - .into_iter() - .filter_map(|br| { - match br { - // We only care about named late bound regions, as we need to add them - // to the 'for<>' section - ty::BrNamed(def_id, name) => Some(GenericParamDef::lifetime(def_id, name)), - _ => None, - } - }) - .collect() - } - - fn make_final_bounds( - &self, - ty_to_bounds: FxHashMap>, - ty_to_fn: FxHashMap)>, - lifetime_to_bounds: FxHashMap>, - ) -> Vec { - ty_to_bounds - .into_iter() - .flat_map(|(ty, mut bounds)| { - if let Some((ref poly_trait, ref output)) = ty_to_fn.get(&ty) { - let mut new_path = poly_trait.trait_.clone(); - let last_segment = new_path.segments.pop().expect("segments were empty"); - - let (old_input, old_output) = match last_segment.args { - GenericArgs::AngleBracketed { args, .. } => { - let types = args - .iter() - .filter_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty.clone()), - _ => None, - }) - .collect(); - (types, None) + // (2) Here, we "flatten" the map one element at a time. All of the elements' sub and super + // regions are connected to each other. For example, if we have a graph that looks like this: + // + // (A, B) - C - (D, E) + // + // where (A, B) are sub regions, and (D,E) are super regions. + // Then, after deleting 'C', the graph will look like this: + // + // ... - A - (D, E, ...) + // ... - B - (D, E, ...) + // (A, B, ...) - D - ... + // (A, B, ...) - E - ... + // + // where '...' signifies the existing sub and super regions of an entry. When two adjacent + // `Region`s are encountered, we've computed a final constraint, and add it to our list. + // Since we make sure to never re-add deleted items, this process will always finish. + while !map.is_empty() { + let target = *map.keys().next().unwrap(); + let deps = map.swap_remove(&target).unwrap(); + + for smaller in &deps.smaller { + for larger in &deps.larger { + match (smaller, larger) { + (&RegionTarget::Region(smaller), &RegionTarget::Region(larger)) => { + if early_bound_region_name(smaller) != early_bound_region_name(larger) { + outlives_predicates + .entry( + early_bound_region_name(larger).expect("no region name found"), + ) + .or_default() + .push(smaller) } - GenericArgs::Parenthesized { inputs, output } => (inputs, output), - }; - - let output = output.as_ref().cloned().map(Box::new); - if old_output.is_some() && old_output != output { - panic!("Output mismatch for {ty:?} {old_output:?} {output:?}"); } - - let new_params = GenericArgs::Parenthesized { inputs: old_input, output }; - - new_path - .segments - .push(PathSegment { name: last_segment.name, args: new_params }); - - bounds.insert(GenericBound::TraitBound( - PolyTrait { - trait_: new_path, - generic_params: poly_trait.generic_params.clone(), - }, - hir::TraitBoundModifier::None, - )); - } - if bounds.is_empty() { - return None; - } - - let mut bounds_vec = bounds.into_iter().collect(); - self.sort_where_bounds(&mut bounds_vec); - - Some(WherePredicate::BoundPredicate { - ty, - bounds: bounds_vec, - bound_params: Vec::new(), - }) - }) - .chain(lifetime_to_bounds.into_iter().filter(|(_, bounds)| !bounds.is_empty()).map( - |(lifetime, bounds)| { - let mut bounds_vec = bounds.into_iter().collect(); - self.sort_where_bounds(&mut bounds_vec); - WherePredicate::RegionPredicate { lifetime, bounds: bounds_vec } - }, - )) - .collect() - } - - /// Converts the calculated `ParamEnv` and lifetime information to a [`clean::Generics`](Generics), suitable for - /// display on the docs page. Cleaning the `Predicates` produces sub-optimal [`WherePredicate`]s, - /// so we fix them up: - /// - /// * Multiple bounds for the same type are coalesced into one: e.g., `T: Copy`, `T: Debug` - /// becomes `T: Copy + Debug` - /// * `Fn` bounds are handled specially - instead of leaving it as `T: Fn(), = - /// K`, we use the dedicated syntax `T: Fn() -> K` - /// * We explicitly add a `?Sized` bound if we didn't find any `Sized` predicates for a type - fn param_env_to_generics( - &mut self, - item_def_id: DefId, - param_env: ty::ParamEnv<'tcx>, - mut existing_predicates: ThinVec, - vid_to_region: FxHashMap>, - ) -> Generics { - debug!( - "param_env_to_generics(item_def_id={:?}, param_env={:?}, \ - existing_predicates={:?})", - item_def_id, param_env, existing_predicates - ); - - let tcx = self.cx.tcx; - - // The `Sized` trait must be handled specially, since we only display it when - // it is *not* required (i.e., '?Sized') - let sized_trait = tcx.require_lang_item(LangItem::Sized, None); - - let mut replacer = RegionReplacer { vid_to_region: &vid_to_region, tcx }; - - let orig_bounds: FxHashSet<_> = tcx.param_env(item_def_id).caller_bounds().iter().collect(); - let clean_where_predicates = param_env - .caller_bounds() - .iter() - .filter(|p| { - !orig_bounds.contains(p) - || match p.kind().skip_binder() { - ty::ClauseKind::Trait(pred) => pred.def_id() == sized_trait, - _ => false, - } - }) - .map(|p| p.fold_with(&mut replacer)); - - let raw_generics = clean_ty_generics( - self.cx, - tcx.generics_of(item_def_id), - tcx.explicit_predicates_of(item_def_id), - ); - let mut generic_params = raw_generics.params; - - debug!("param_env_to_generics({item_def_id:?}): generic_params={generic_params:?}"); - - let mut has_sized = FxHashSet::default(); - let mut ty_to_bounds: FxHashMap<_, FxHashSet<_>> = Default::default(); - let mut lifetime_to_bounds: FxHashMap<_, FxHashSet<_>> = Default::default(); - let mut ty_to_traits: FxHashMap> = Default::default(); - - let mut ty_to_fn: FxHashMap)> = Default::default(); - - // FIXME: This code shares much of the logic found in `clean_ty_generics` and - // `simplify::where_clause`. Consider deduplicating it to avoid diverging - // implementations. - // Further, the code below does not merge (partially re-sugared) bounds like - // `Tr` & `Tr` and it does not render higher-ranked parameters - // originating from equality predicates. - for p in clean_where_predicates { - let (orig_p, p) = (p, clean_predicate(p, self.cx)); - if p.is_none() { - continue; - } - let p = p.unwrap(); - match p { - WherePredicate::BoundPredicate { ty, mut bounds, .. } => { - // Writing a projection trait bound of the form - // ::Name : ?Sized - // is illegal, because ?Sized bounds can only - // be written in the (here, nonexistent) definition - // of the type. - // Therefore, we make sure that we never add a ?Sized - // bound for projections - if let Type::QPath { .. } = ty { - has_sized.insert(ty.clone()); - } - - if bounds.is_empty() { - continue; - } - - let mut for_generics = self.extract_for_generics(orig_p); - - assert!(bounds.len() == 1); - let mut b = bounds.pop().expect("bounds were empty"); - - if b.is_sized_bound(self.cx) { - has_sized.insert(ty.clone()); - } else if !b - .get_trait_path() - .and_then(|trait_| { - ty_to_traits - .get(&ty) - .map(|bounds| bounds.contains(&strip_path_generics(trait_))) - }) - .unwrap_or(false) - { - // If we've already added a projection bound for the same type, don't add - // this, as it would be a duplicate - - // Handle any 'Fn/FnOnce/FnMut' bounds specially, - // as we want to combine them with any 'Output' qpaths - // later - - let is_fn = match b { - GenericBound::TraitBound(ref mut p, _) => { - // Insert regions into the for_generics hash map first, to ensure - // that we don't end up with duplicate bounds (e.g., for<'b, 'b>) - for_generics.extend(p.generic_params.drain(..)); - p.generic_params.extend(for_generics); - self.is_fn_trait(&p.trait_) - } - _ => false, - }; - - let poly_trait = b.get_poly_trait().expect("Cannot get poly trait"); - - if is_fn { - ty_to_fn - .entry(ty.clone()) - .and_modify(|e| *e = (poly_trait.clone(), e.1.clone())) - .or_insert(((poly_trait.clone()), None)); - - ty_to_bounds.entry(ty.clone()).or_default(); - } else { - ty_to_bounds.entry(ty.clone()).or_default().insert(b.clone()); + (&RegionTarget::RegionVid(_), &RegionTarget::Region(_)) => { + if let IndexEntry::Occupied(v) = map.entry(*smaller) { + let smaller_deps = v.into_mut(); + smaller_deps.larger.insert(*larger); + smaller_deps.larger.swap_remove(&target); } } - } - WherePredicate::RegionPredicate { lifetime, bounds } => { - lifetime_to_bounds.entry(lifetime).or_default().extend(bounds); - } - WherePredicate::EqPredicate { lhs, rhs } => { - match lhs { - Type::QPath(box QPathData { - ref assoc, - ref self_type, - trait_: Some(ref trait_), - .. - }) => { - let ty = &*self_type; - let mut new_trait = trait_.clone(); - - if self.is_fn_trait(trait_) && assoc.name == sym::Output { - ty_to_fn - .entry(ty.clone()) - .and_modify(|e| { - *e = (e.0.clone(), Some(rhs.ty().unwrap().clone())) - }) - .or_insert(( - PolyTrait { - trait_: trait_.clone(), - generic_params: Vec::new(), - }, - Some(rhs.ty().unwrap().clone()), - )); - continue; - } - - let args = &mut new_trait - .segments - .last_mut() - .expect("segments were empty") - .args; - - match args { - // Convert something like ' = u8' - // to 'T: Iterator' - GenericArgs::AngleBracketed { ref mut bindings, .. } => { - bindings.push(TypeBinding { - assoc: assoc.clone(), - kind: TypeBindingKind::Equality { term: rhs }, - }); - } - GenericArgs::Parenthesized { .. } => { - existing_predicates.push(WherePredicate::EqPredicate { - lhs: lhs.clone(), - rhs, - }); - continue; // If something other than a Fn ends up - // with parentheses, leave it alone - } - } - - let bounds = ty_to_bounds.entry(ty.clone()).or_default(); - - bounds.insert(GenericBound::TraitBound( - PolyTrait { trait_: new_trait, generic_params: Vec::new() }, - hir::TraitBoundModifier::None, - )); - - // Remove any existing 'plain' bound (e.g., 'T: Iterator`) so - // that we don't see a - // duplicate bound like `T: Iterator + Iterator` - // on the docs page. - bounds.remove(&GenericBound::TraitBound( - PolyTrait { trait_: trait_.clone(), generic_params: Vec::new() }, - hir::TraitBoundModifier::None, - )); - // Avoid creating any new duplicate bounds later in the outer - // loop - ty_to_traits.entry(ty.clone()).or_default().insert(trait_.clone()); + (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => { + if let IndexEntry::Occupied(v) = map.entry(*larger) { + let deps = v.into_mut(); + deps.smaller.insert(*smaller); + deps.smaller.swap_remove(&target); } - _ => panic!("Unexpected LHS {lhs:?} for {item_def_id:?}"), } - } - }; - } - - let final_bounds = self.make_final_bounds(ty_to_bounds, ty_to_fn, lifetime_to_bounds); - - existing_predicates.extend(final_bounds); - - for param in generic_params.iter_mut() { - match param.kind { - GenericParamDefKind::Type { ref mut default, ref mut bounds, .. } => { - // We never want something like `impl`. - default.take(); - let generic_ty = Type::Generic(param.name); - if !has_sized.contains(&generic_ty) { - bounds.insert(0, GenericBound::maybe_sized(self.cx)); + (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => { + if let IndexEntry::Occupied(v) = map.entry(*smaller) { + let smaller_deps = v.into_mut(); + smaller_deps.larger.insert(*larger); + smaller_deps.larger.swap_remove(&target); + } + if let IndexEntry::Occupied(v) = map.entry(*larger) { + let larger_deps = v.into_mut(); + larger_deps.smaller.insert(*smaller); + larger_deps.smaller.swap_remove(&target); + } } } - GenericParamDefKind::Lifetime { .. } => {} - GenericParamDefKind::Const { ref mut default, .. } => { - // We never want something like `impl` - default.take(); - } } } - - self.sort_where_predicates(&mut existing_predicates); - - Generics { params: generic_params, where_predicates: existing_predicates } - } - - /// Ensure that the predicates are in a consistent order. The precise - /// ordering doesn't actually matter, but it's important that - /// a given set of predicates always appears in the same order - - /// both for visual consistency between 'rustdoc' runs, and to - /// make writing tests much easier - #[inline] - fn sort_where_predicates(&self, predicates: &mut [WherePredicate]) { - // We should never have identical bounds - and if we do, - // they're visually identical as well. Therefore, using - // an unstable sort is fine. - self.unstable_debug_sort(predicates); - } - - /// Ensure that the bounds are in a consistent order. The precise - /// ordering doesn't actually matter, but it's important that - /// a given set of bounds always appears in the same order - - /// both for visual consistency between 'rustdoc' runs, and to - /// make writing tests much easier - #[inline] - fn sort_where_bounds(&self, bounds: &mut Vec) { - // We should never have identical bounds - and if we do, - // they're visually identical as well. Therefore, using - // an unstable sort is fine. - self.unstable_debug_sort(bounds); } - /// This might look horrendously hacky, but it's actually not that bad. - /// - /// For performance reasons, we use several different FxHashMaps - /// in the process of computing the final set of where predicates. - /// However, the iteration order of a HashMap is completely unspecified. - /// In fact, the iteration of an FxHashMap can even vary between platforms, - /// since FxHasher has different behavior for 32-bit and 64-bit platforms. - /// - /// Obviously, it's extremely undesirable for documentation rendering - /// to be dependent on the platform it's run on. Apart from being confusing - /// to end users, it makes writing tests much more difficult, as predicates - /// can appear in any order in the final result. - /// - /// To solve this problem, we sort WherePredicates and GenericBounds - /// by their Debug string. The thing to keep in mind is that we don't really - /// care what the final order is - we're synthesizing an impl or bound - /// ourselves, so any order can be considered equally valid. By sorting the - /// predicates and bounds, however, we ensure that for a given codebase, all - /// auto-trait impls always render in exactly the same way. - /// - /// Using the Debug implementation for sorting prevents us from needing to - /// write quite a bit of almost entirely useless code (e.g., how should two - /// Types be sorted relative to each other). It also allows us to solve the - /// problem for both WherePredicates and GenericBounds at the same time. This - /// approach is probably somewhat slower, but the small number of items - /// involved (impls rarely have more than a few bounds) means that it - /// shouldn't matter in practice. - fn unstable_debug_sort(&self, vec: &mut [T]) { - vec.sort_by_cached_key(|x| format!("{x:?}")) - } + let region_params: FxIndexSet<_> = generics + .params + .iter() + .filter_map(|param| match param.kind { + ty::GenericParamDefKind::Lifetime => Some(param.name), + _ => None, + }) + .collect(); - fn is_fn_trait(&self, path: &Path) -> bool { - let tcx = self.cx.tcx; - let did = path.def_id(); - did == tcx.require_lang_item(LangItem::Fn, None) - || did == tcx.require_lang_item(LangItem::FnMut, None) - || did == tcx.require_lang_item(LangItem::FnOnce, None) - } + region_params + .iter() + .filter_map(|&name| { + let bounds: FxIndexSet<_> = outlives_predicates + .get(&name)? + .iter() + .map(|®ion| { + let lifetime = early_bound_region_name(region) + .inspect(|name| assert!(region_params.contains(name))) + .map(|name| Lifetime(name)) + .unwrap_or(Lifetime::statik()); + clean::GenericBound::Outlives(lifetime) + }) + .collect(); + if bounds.is_empty() { + return None; + } + Some(clean::WherePredicate::RegionPredicate { + lifetime: Lifetime(name), + bounds: bounds.into_iter().collect(), + }) + }) + .collect() } -fn region_name(region: Region<'_>) -> Option { +fn early_bound_region_name(region: Region<'_>) -> Option { match *region { ty::ReEarlyParam(r) => Some(r.name), _ => None, } } - -/// Replaces all [`ty::RegionVid`]s in a type with [`ty::Region`]s, using the provided map. -struct RegionReplacer<'a, 'tcx> { - vid_to_region: &'a FxHashMap>, - tcx: TyCtxt<'tcx>, -} - -impl<'a, 'tcx> TypeFolder> for RegionReplacer<'a, 'tcx> { - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - - fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { - match *r { - // These are the regions that can be seen in the AST. - ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned().unwrap_or(r), - ty::ReEarlyParam(_) | ty::ReStatic | ty::ReBound(..) | ty::ReError(_) => r, - r => bug!("unexpected region: {r:?}"), - } - } -} diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 0cdf52bfb0046..0cd71366288c2 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -21,10 +21,8 @@ use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId, LOCAL_CRATE}; use rustc_hir::PredicateOrigin; use rustc_hir_analysis::lower_ty; -use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData}; use rustc_middle::metadata::Reexport; use rustc_middle::middle::resolve_bound_vars as rbv; -use rustc_middle::ty::fold::TypeFolder; use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt}; @@ -35,9 +33,7 @@ use rustc_span::{self, ExpnKind}; use rustc_trait_selection::traits::wf::object_region_bounds; use std::borrow::Cow; -use std::collections::hash_map::Entry; use std::collections::BTreeMap; -use std::hash::Hash; use std::mem; use thin_vec::ThinVec; @@ -502,6 +498,7 @@ fn projection_to_path_segment<'tcx>( fn clean_generic_param_def<'tcx>( def: &ty::GenericParamDef, + defaults: ParamDefaults, cx: &mut DocContext<'tcx>, ) -> GenericParamDef { let (name, kind) = match def.kind { @@ -509,7 +506,9 @@ fn clean_generic_param_def<'tcx>( (def.name, GenericParamDefKind::Lifetime { outlives: ThinVec::new() }) } ty::GenericParamDefKind::Type { has_default, synthetic, .. } => { - let default = if has_default { + let default = if let ParamDefaults::Yes = defaults + && has_default + { Some(clean_middle_ty( ty::Binder::dummy(cx.tcx.type_of(def.def_id).instantiate_identity()), cx, @@ -542,11 +541,14 @@ fn clean_generic_param_def<'tcx>( Some(def.def_id), None, )), - default: match has_default { - true => Some(Box::new( + default: if let ParamDefaults::Yes = defaults + && has_default + { + Some(Box::new( cx.tcx.const_param_default(def.def_id).instantiate_identity().to_string(), - )), - false => None, + )) + } else { + None }, is_host_effect, }, @@ -556,6 +558,12 @@ fn clean_generic_param_def<'tcx>( GenericParamDef { name, def_id: def.def_id, kind } } +/// Whether to clean generic parameter defaults or not. +enum ParamDefaults { + Yes, + No, +} + fn clean_generic_param<'tcx>( cx: &mut DocContext<'tcx>, generics: Option<&hir::Generics<'tcx>>, @@ -759,34 +767,30 @@ fn clean_ty_generics<'tcx>( gens: &ty::Generics, preds: ty::GenericPredicates<'tcx>, ) -> Generics { - // Don't populate `cx.impl_trait_bounds` before `clean`ning `where` clauses, + // Don't populate `cx.impl_trait_bounds` before cleanning where clauses, // since `Clean for ty::Predicate` would consume them. let mut impl_trait = BTreeMap::>::default(); - // Bounds in the type_params and lifetimes fields are repeated in the - // predicates field (see rustc_hir_analysis::collect::ty_generics), so remove - // them. - let stripped_params = gens + let params: ThinVec<_> = gens .params .iter() - .filter_map(|param| match param.kind { - ty::GenericParamDefKind::Lifetime if param.is_anonymous_lifetime() => None, - ty::GenericParamDefKind::Lifetime => Some(clean_generic_param_def(param, cx)), + .filter(|param| match param.kind { + ty::GenericParamDefKind::Lifetime => !param.is_anonymous_lifetime(), ty::GenericParamDefKind::Type { synthetic, .. } => { if param.name == kw::SelfUpper { - assert_eq!(param.index, 0); - return None; + debug_assert_eq!(param.index, 0); + return false; } if synthetic { impl_trait.insert(param.index, vec![]); - return None; + return false; } - Some(clean_generic_param_def(param, cx)) + true } - ty::GenericParamDefKind::Const { is_host_effect: true, .. } => None, - ty::GenericParamDefKind::Const { .. } => Some(clean_generic_param_def(param, cx)), + ty::GenericParamDefKind::Const { is_host_effect, .. } => !is_host_effect, }) - .collect::>(); + .map(|param| clean_generic_param_def(param, ParamDefaults::Yes, cx)) + .collect(); // param index -> [(trait DefId, associated type name & generics, term)] let mut impl_trait_proj = @@ -882,56 +886,13 @@ fn clean_ty_generics<'tcx>( // Now that `cx.impl_trait_bounds` is populated, we can process // remaining predicates which could contain `impl Trait`. - let mut where_predicates = - where_predicates.into_iter().flat_map(|p| clean_predicate(*p, cx)).collect::>(); - - // In the surface language, all type parameters except `Self` have an - // implicit `Sized` bound unless removed with `?Sized`. - // However, in the list of where-predicates below, `Sized` appears like a - // normal bound: It's either present (the type is sized) or - // absent (the type might be unsized) but never *maybe* (i.e. `?Sized`). - // - // This is unsuitable for rendering. - // Thus, as a first step remove all `Sized` bounds that should be implicit. - // - // Note that associated types also have an implicit `Sized` bound but we - // don't actually know the set of associated types right here so that's - // handled when cleaning associated types. - let mut sized_params = FxHashSet::default(); - where_predicates.retain(|pred| { - if let WherePredicate::BoundPredicate { ty: Generic(g), bounds, .. } = pred - && *g != kw::SelfUpper - && bounds.iter().any(|b| b.is_sized_bound(cx)) - { - sized_params.insert(*g); - false - } else { - true - } - }); - - // As a final step, go through the type parameters again and insert a - // `?Sized` bound for each one we didn't find to be `Sized`. - for tp in &stripped_params { - if let types::GenericParamDefKind::Type { .. } = tp.kind - && !sized_params.contains(&tp.name) - { - where_predicates.push(WherePredicate::BoundPredicate { - ty: Type::Generic(tp.name), - bounds: vec![GenericBound::maybe_sized(cx)], - bound_params: Vec::new(), - }) - } - } - - // It would be nice to collect all of the bounds on a type and recombine - // them if possible, to avoid e.g., `where T: Foo, T: Bar, T: Sized, T: 'a` - // and instead see `where T: Foo + Bar + Sized + 'a` + let where_predicates = + where_predicates.into_iter().flat_map(|p| clean_predicate(*p, cx)).collect(); - Generics { - params: stripped_params, - where_predicates: simplify::where_clauses(cx, where_predicates), - } + let mut generics = Generics { params, where_predicates }; + simplify::sized_bounds(cx, &mut generics); + generics.where_predicates = simplify::where_clauses(cx, generics.where_predicates); + generics } fn clean_ty_alias_inner_type<'tcx>( diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index c35fb9ec78875..5a3ccb6239a06 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -12,6 +12,7 @@ //! bounds by special casing scenarios such as these. Fun! use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::unord::UnordSet; use rustc_hir::def_id::DefId; use rustc_middle::ty; use thin_vec::ThinVec; @@ -21,7 +22,7 @@ use crate::clean::GenericArgs as PP; use crate::clean::WherePredicate as WP; use crate::core::DocContext; -pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec) -> ThinVec { +pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: ThinVec) -> ThinVec { // First, partition the where clause into its separate components. // // We use `FxIndexMap` so that the insertion order is preserved to prevent messing up to @@ -128,6 +129,48 @@ fn trait_is_same_or_supertrait(cx: &DocContext<'_>, child: DefId, trait_: DefId) .any(|did| trait_is_same_or_supertrait(cx, did, trait_)) } +pub(crate) fn sized_bounds(cx: &mut DocContext<'_>, generics: &mut clean::Generics) { + let mut sized_params = UnordSet::new(); + + // In the surface language, all type parameters except `Self` have an + // implicit `Sized` bound unless removed with `?Sized`. + // However, in the list of where-predicates below, `Sized` appears like a + // normal bound: It's either present (the type is sized) or + // absent (the type might be unsized) but never *maybe* (i.e. `?Sized`). + // + // This is unsuitable for rendering. + // Thus, as a first step remove all `Sized` bounds that should be implicit. + // + // Note that associated types also have an implicit `Sized` bound but we + // don't actually know the set of associated types right here so that + // should be handled when cleaning associated types. + generics.where_predicates.retain(|pred| { + if let WP::BoundPredicate { ty: clean::Generic(param), bounds, .. } = pred + && *param != rustc_span::symbol::kw::SelfUpper + && bounds.iter().any(|b| b.is_sized_bound(cx)) + { + sized_params.insert(*param); + false + } else { + true + } + }); + + // As a final step, go through the type parameters again and insert a + // `?Sized` bound for each one we didn't find to be `Sized`. + for param in &generics.params { + if let clean::GenericParamDefKind::Type { .. } = param.kind + && !sized_params.contains(¶m.name) + { + generics.where_predicates.push(WP::BoundPredicate { + ty: clean::Type::Generic(param.name), + bounds: vec![clean::GenericBound::maybe_sized(cx)], + bound_params: Vec::new(), + }) + } + } +} + /// Move bounds that are (likely) directly attached to generic parameters from the where-clause to /// the respective parameter. /// diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index a51f6360df2a4..6793ea9f485f3 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1277,13 +1277,6 @@ impl GenericBound { false } - pub(crate) fn get_poly_trait(&self) -> Option { - if let GenericBound::TraitBound(ref p, _) = *self { - return Some(p.clone()); - } - None - } - pub(crate) fn get_trait_path(&self) -> Option { if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, _) = *self { Some(trait_.clone()) diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 977b4bb45b620..04d5b84f5e0ce 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -1,4 +1,4 @@ -use crate::clean::auto_trait::AutoTraitFinder; +use crate::clean::auto_trait::synthesize_auto_trait_impls; use crate::clean::blanket_impl::BlanketImplFinder; use crate::clean::render_macro_matchers::render_macro_matcher; use crate::clean::{ @@ -251,15 +251,6 @@ pub(super) fn clean_middle_path<'tcx>( } } -/// Remove the generic arguments from a path. -pub(crate) fn strip_path_generics(mut path: Path) -> Path { - for ps in path.segments.iter_mut() { - ps.args = GenericArgs::AngleBracketed { args: Default::default(), bindings: ThinVec::new() } - } - - path -} - pub(crate) fn qpath_to_string(p: &hir::QPath<'_>) -> String { let segments = match *p { hir::QPath::Resolved(_, path) => &path.segments, @@ -494,7 +485,7 @@ pub(crate) fn get_auto_trait_and_blanket_impls( .sess() .prof .generic_activity("get_auto_trait_impls") - .run(|| AutoTraitFinder::new(cx).get_auto_trait_impls(item_def_id)); + .run(|| synthesize_auto_trait_impls(cx, item_def_id)); let blanket_impls = cx .sess() .prof diff --git a/tests/rustdoc/synthetic_auto/bounds.rs b/tests/rustdoc/synthetic_auto/bounds.rs new file mode 100644 index 0000000000000..17528d01c8d28 --- /dev/null +++ b/tests/rustdoc/synthetic_auto/bounds.rs @@ -0,0 +1,21 @@ +pub struct Outer(Inner); +pub struct Inner(T); + +// @has bounds/struct.Outer.html +// @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \ +// "impl Unpin for Outerwhere \ +// T: for<'any> Trait = (), X = ()>," + +impl std::marker::Unpin for Inner +where + T: for<'any> Trait = (), X = ()>, +{} + +pub trait Trait: SuperTrait { + type A; + type B<'a>; +} + +pub trait SuperTrait { + type X; +} diff --git a/tests/rustdoc/synthetic_auto/complex.rs b/tests/rustdoc/synthetic_auto/complex.rs index 4c39f0bf1e07f..2722f6d338fe5 100644 --- a/tests/rustdoc/synthetic_auto/complex.rs +++ b/tests/rustdoc/synthetic_auto/complex.rs @@ -21,8 +21,8 @@ mod foo { // @has complex/struct.NotOuter.html // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \ -// "impl<'a, T, K: ?Sized> Send for Outer<'a, T, K>where K: for<'b> Fn((&'b bool, &'a u8)) \ -// -> &'b i8, T: MyTrait<'a>, >::MyItem: Copy, 'a: 'static" +// "impl<'a, T, K> Send for Outer<'a, T, K>where 'a: 'static, T: MyTrait<'a>, \ +// K: for<'b> Fn((&'b bool, &'a u8)) -> &'b i8 + ?Sized, >::MyItem: Copy," pub use foo::{Foo, Inner as NotInner, MyTrait as NotMyTrait, Outer as NotOuter}; diff --git a/tests/rustdoc/synthetic_auto/lifetimes.rs b/tests/rustdoc/synthetic_auto/lifetimes.rs index 71265b3078a06..23e1efdaeef19 100644 --- a/tests/rustdoc/synthetic_auto/lifetimes.rs +++ b/tests/rustdoc/synthetic_auto/lifetimes.rs @@ -10,7 +10,7 @@ where // @has lifetimes/struct.Foo.html // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \ -// "impl<'c, K> Send for Foo<'c, K>where K: for<'b> Fn(&'b bool) -> &'c u8, 'c: 'static" +// "impl<'c, K> Send for Foo<'c, K>where 'c: 'static, K: for<'b> Fn(&'b bool) -> &'c u8," // // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \ // "impl<'c, K> Sync for Foo<'c, K>where K: Sync" diff --git a/tests/rustdoc/synthetic_auto/no-redundancy.rs b/tests/rustdoc/synthetic_auto/no-redundancy.rs index d30b38dd4dc1d..64dab429647ae 100644 --- a/tests/rustdoc/synthetic_auto/no-redundancy.rs +++ b/tests/rustdoc/synthetic_auto/no-redundancy.rs @@ -1,6 +1,3 @@ -// FIXME(fmease, #119216): Reenable this test! -//@ ignore-test - pub struct Inner { field: T, } @@ -13,7 +10,7 @@ where // @has no_redundancy/struct.Outer.html // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \ -// "impl Send for Outerwhere T: Send + Copy" +// "impl Send for Outerwhere T: Copy + Send" pub struct Outer { inner_field: Inner, } diff --git a/tests/rustdoc/synthetic_auto/project.rs b/tests/rustdoc/synthetic_auto/project.rs index 7c9412ae96243..f4ede76e6debf 100644 --- a/tests/rustdoc/synthetic_auto/project.rs +++ b/tests/rustdoc/synthetic_auto/project.rs @@ -24,11 +24,11 @@ where // @has project/struct.Foo.html // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \ -// "impl<'c, K> Send for Foo<'c, K>where K: MyTrait, 'c: 'static" +// "impl<'c, K> Send for Foo<'c, K>where 'c: 'static, K: MyTrait," // // @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \ -// "impl<'c, K> Sync for Foo<'c, K>where K: MyTrait, ::MyItem: OtherTrait, \ -// 'c: 'static," +// "impl<'c, K> Sync for Foo<'c, K>where 'c: 'static, K: MyTrait, \ +// ::MyItem: OtherTrait," pub struct Foo<'c, K: 'c> { inner_field: Inner<'c, K>, }