rustdoc: synthetic impls: auto traits: Fx{Hash↦Index}{Map,Set}

This commit is contained in:
León Orell Valerian Liehr 2024-04-01 07:12:22 +02:00
parent a3cfa031fa
commit cbd593ed18
No known key found for this signature in database
GPG Key ID: D17A07215F68E713
7 changed files with 64 additions and 158 deletions

View File

@ -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;
@ -35,17 +35,10 @@ pub enum AutoTraitResult<A> {
NegativeImpl,
}
#[allow(dead_code)]
impl<A> AutoTraitResult<A> {
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<ty::RegionVid, ty::Region<'cx>>,
pub vid_to_region: FxIndexMap<ty::RegionVid, ty::Region<'cx>>,
}
pub struct AutoTraitFinder<'tcx> {
@ -114,7 +107,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 +232,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
ty: Ty<'tcx>,
param_env: ty::ParamEnv<'tcx>,
user_env: ty::ParamEnv<'tcx>,
fresh_preds: &mut FxHashSet<ty::Predicate<'tcx>>,
fresh_preds: &mut FxIndexSet<ty::Predicate<'tcx>>,
) -> Option<(ty::ParamEnv<'tcx>, ty::ParamEnv<'tcx>)> {
let tcx = infcx.tcx;
@ -252,7 +245,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 +466,9 @@ impl<'tcx> AutoTraitFinder<'tcx> {
fn map_vid_to_region<'cx>(
&self,
regions: &RegionConstraintData<'cx>,
) -> FxHashMap<ty::RegionVid, ty::Region<'cx>> {
let mut vid_map: FxHashMap<RegionTarget<'cx>, RegionDeps<'cx>> = FxHashMap::default();
let mut finished_map = FxHashMap::default();
) -> FxIndexMap<ty::RegionVid, ty::Region<'cx>> {
let mut vid_map = FxIndexMap::<RegionTarget<'cx>, RegionDeps<'cx>>::default();
let mut finished_map = FxIndexMap::default();
for (constraint, _) in &regions.constraints {
match constraint {
@ -513,25 +506,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 +532,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 +548,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
}
}
}
finished_map
}
@ -588,7 +577,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
ty: Ty<'_>,
nested: impl Iterator<Item = PredicateObligation<'tcx>>,
computed_preds: &mut FxIndexSet<ty::Predicate<'tcx>>,
fresh_preds: &mut FxHashSet<ty::Predicate<'tcx>>,
fresh_preds: &mut FxIndexSet<ty::Predicate<'tcx>>,
predicates: &mut VecDeque<ty::PolyTraitPredicate<'tcx>>,
selcx: &mut SelectionContext<'_, 'tcx>,
) -> bool {

View File

@ -15,18 +15,15 @@ enum RegionTarget<'tcx> {
#[derive(Default, Debug, Clone)]
struct RegionDeps<'tcx> {
larger: FxHashSet<RegionTarget<'tcx>>,
smaller: FxHashSet<RegionTarget<'tcx>>,
larger: FxIndexSet<RegionTarget<'tcx>>,
smaller: FxIndexSet<RegionTarget<'tcx>>,
}
pub(crate) struct AutoTraitFinder<'a, 'tcx> {
pub(crate) cx: &'a mut core::DocContext<'tcx>,
}
impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx>
where
'tcx: 'a, // should be an implied bound; rustc bug #98852.
{
impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> Self {
AutoTraitFinder { cx }
}
@ -158,7 +155,7 @@ where
auto_traits
}
fn get_lifetime(region: Region<'_>, names_map: &FxHashMap<Symbol, Lifetime>) -> Lifetime {
fn get_lifetime(region: Region<'_>, names_map: &FxIndexMap<Symbol, Lifetime>) -> Lifetime {
region_name(region)
.map(|name| {
names_map
@ -181,15 +178,15 @@ where
/// existing inference/solver code do what we want.
fn handle_lifetimes<'cx>(
regions: &RegionConstraintData<'cx>,
names_map: &FxHashMap<Symbol, Lifetime>,
names_map: &FxIndexMap<Symbol, Lifetime>,
) -> ThinVec<WherePredicate> {
// 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 finished: FxIndexMap<_, Vec<_>> = Default::default();
let mut vid_map: FxHashMap<RegionTarget<'_>, RegionDeps<'_>> = Default::default();
let mut vid_map: FxIndexMap<RegionTarget<'_>, 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
@ -245,8 +242,8 @@ where
// 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");
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() {
@ -260,30 +257,30 @@ where
}
}
(&RegionTarget::RegionVid(_), &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);
smaller_deps.larger.remove(&target);
smaller_deps.larger.swap_remove(&target);
}
}
(&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => {
if let Entry::Occupied(v) = vid_map.entry(*larger) {
if let IndexEntry::Occupied(v) = vid_map.entry(*larger) {
let deps = v.into_mut();
deps.smaller.insert(*smaller);
deps.smaller.remove(&target);
deps.smaller.swap_remove(&target);
}
}
(&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);
smaller_deps.larger.remove(&target);
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);
larger_deps.smaller.remove(&target);
larger_deps.smaller.swap_remove(&target);
}
}
}
@ -295,7 +292,7 @@ where
.iter()
.flat_map(|(name, lifetime)| {
let empty = Vec::new();
let bounds: FxHashSet<GenericBound> = finished
let bounds: FxIndexSet<GenericBound> = finished
.get(name)
.unwrap_or(&empty)
.iter()
@ -315,7 +312,7 @@ where
lifetime_predicates
}
fn extract_for_generics(&self, pred: ty::Clause<'tcx>) -> FxHashSet<GenericParamDef> {
fn extract_for_generics(&self, pred: ty::Clause<'tcx>) -> FxIndexSet<GenericParamDef> {
let bound_predicate = pred.kind();
let tcx = self.cx.tcx;
let regions =
@ -324,7 +321,7 @@ where
.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(),
_ => return FxIndexSet::default(),
};
regions
@ -342,9 +339,9 @@ where
fn make_final_bounds(
&self,
ty_to_bounds: FxHashMap<Type, FxHashSet<GenericBound>>,
ty_to_fn: FxHashMap<Type, (PolyTrait, Option<Type>)>,
lifetime_to_bounds: FxHashMap<Lifetime, FxHashSet<GenericBound>>,
ty_to_bounds: FxIndexMap<Type, FxIndexSet<GenericBound>>,
ty_to_fn: FxIndexMap<Type, (PolyTrait, Option<Type>)>,
lifetime_to_bounds: FxIndexMap<Lifetime, FxIndexSet<GenericBound>>,
) -> Vec<WherePredicate> {
ty_to_bounds
.into_iter()
@ -390,20 +387,16 @@ where
return None;
}
let mut bounds_vec = bounds.into_iter().collect();
self.sort_where_bounds(&mut bounds_vec);
Some(WherePredicate::BoundPredicate {
ty,
bounds: bounds_vec,
bounds: bounds.into_iter().collect(),
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 }
|(lifetime, bounds)| WherePredicate::RegionPredicate {
lifetime,
bounds: bounds.into_iter().collect(),
},
))
.collect()
@ -418,19 +411,14 @@ where
/// * `Fn` bounds are handled specially - instead of leaving it as `T: Fn(), <T as Fn::Output> =
/// 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
#[instrument(level = "debug", skip(self, vid_to_region))]
fn param_env_to_generics(
&mut self,
item_def_id: DefId,
param_env: ty::ParamEnv<'tcx>,
mut existing_predicates: ThinVec<WherePredicate>,
vid_to_region: FxHashMap<ty::RegionVid, ty::Region<'tcx>>,
vid_to_region: FxIndexMap<ty::RegionVid, ty::Region<'tcx>>,
) -> 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
@ -439,6 +427,7 @@ where
let mut replacer = RegionReplacer { vid_to_region: &vid_to_region, tcx };
// FIXME(fmease): Remove this!
let orig_bounds: FxHashSet<_> = tcx.param_env(item_def_id).caller_bounds().iter().collect();
let clean_where_predicates = param_env
.caller_bounds()
@ -461,12 +450,11 @@ where
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<Type, FxHashSet<Path>> = Default::default();
let mut ty_to_fn: FxHashMap<Type, (PolyTrait, Option<Type>)> = Default::default();
let mut has_sized = FxHashSet::default(); // NOTE(fmease): not used for iteration
let mut ty_to_bounds = FxIndexMap::<_, FxIndexSet<_>>::default();
let mut lifetime_to_bounds = FxIndexMap::<_, FxIndexSet<_>>::default();
let mut ty_to_traits = FxIndexMap::<Type, FxIndexSet<Path>>::default();
let mut ty_to_fn = FxIndexMap::<Type, (PolyTrait, Option<Type>)>::default();
// FIXME: This code shares much of the logic found in `clean_ty_generics` and
// `simplify::where_clause`. Consider deduplicating it to avoid diverging
@ -526,7 +514,7 @@ where
// 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_)
tcx.is_fn_trait(p.trait_.def_id())
}
_ => false,
};
@ -559,7 +547,7 @@ where
let ty = &*self_type;
let mut new_trait = trait_.clone();
if self.is_fn_trait(trait_) && assoc.name == sym::Output {
if tcx.is_fn_trait(trait_.def_id()) && assoc.name == sym::Output {
ty_to_fn
.entry(ty.clone())
.and_modify(|e| {
@ -611,7 +599,7 @@ where
// that we don't see a
// duplicate bound like `T: Iterator + Iterator<Item=u8>`
// on the docs page.
bounds.remove(&GenericBound::TraitBound(
bounds.swap_remove(&GenericBound::TraitBound(
PolyTrait { trait_: trait_.clone(), generic_params: Vec::new() },
hir::TraitBoundModifier::None,
));
@ -647,75 +635,8 @@ where
}
}
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<GenericBound>) {
// 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<T: Debug>(&self, vec: &mut [T]) {
vec.sort_by_cached_key(|x| format!("{x:?}"))
}
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)
}
}
fn region_name(region: Region<'_>) -> Option<Symbol> {
@ -727,7 +648,7 @@ fn region_name(region: Region<'_>) -> Option<Symbol> {
/// 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<ty::RegionVid, ty::Region<'tcx>>,
vid_to_region: &'a FxIndexMap<ty::RegionVid, ty::Region<'tcx>>,
tcx: TyCtxt<'tcx>,
}

View File

@ -35,7 +35,6 @@ 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;

View File

@ -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>, <T as MyTrait<'a>>::MyItem: Copy, 'a: 'static"
// "impl<'a, T, K: ?Sized> Send for Outer<'a, T, K>where 'a: 'static, T: MyTrait<'a>, \
// K: for<'b> Fn((&'b bool, &'a u8)) -> &'b i8, <T as MyTrait<'a>>::MyItem: Copy,"
pub use foo::{Foo, Inner as NotInner, MyTrait as NotMyTrait, Outer as NotOuter};

View File

@ -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"

View File

@ -1,6 +1,3 @@
// FIXME(fmease, #119216): Reenable this test!
//@ ignore-test
pub struct Inner<T> {
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<T> Send for Outer<T>where T: Send + Copy"
// "impl<T> Send for Outer<T>where T: Copy + Send"
pub struct Outer<T> {
inner_field: Inner<T>,
}

View File

@ -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<MyItem = bool>, 'c: 'static"
// "impl<'c, K> Send for Foo<'c, K>where 'c: 'static, K: MyTrait<MyItem = bool>,"
//
// @has - '//*[@id="synthetic-implementations-list"]//*[@class="impl"]//h3[@class="code-header"]' \
// "impl<'c, K> Sync for Foo<'c, K>where K: MyTrait, <K as MyTrait>::MyItem: OtherTrait, \
// 'c: 'static,"
// "impl<'c, K> Sync for Foo<'c, K>where 'c: 'static, K: MyTrait, \
// <K as MyTrait>::MyItem: OtherTrait,"
pub struct Foo<'c, K: 'c> {
inner_field: Inner<'c, K>,
}