Skip to content

Commit 9db0b86

Browse files
committed
rustdoc: Cache traits implemented by a type
This avoids taking the slow path several thousand times in a row. - Fallback to all traits if the traits in scope are unknown - Use a rustdoc thread_local cache instead of a query The set of traits implemented by a type is not stable across crates: there could be new traits added in a new crate. - Use DocContext instead of a thread-local
1 parent 42232ba commit 9db0b86

File tree

2 files changed

+65
-45
lines changed

2 files changed

+65
-45
lines changed

src/librustdoc/core.rs

+6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ pub struct DocContext<'tcx> {
6969
pub auto_traits: Vec<DefId>,
7070
/// The options given to rustdoc that could be relevant to a pass.
7171
pub render_options: RenderOptions,
72+
/// The traits implemented by a given type.
73+
///
74+
/// See `collect_intra_doc_links::traits_implemented_by` for more details.
75+
/// `map<type, set<trait>>`
76+
pub type_trait_cache: RefCell<FxHashMap<DefId, FxHashSet<DefId>>>,
7277
}
7378

7479
impl<'tcx> DocContext<'tcx> {
@@ -510,6 +515,7 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
510515
.filter(|trait_def_id| tcx.trait_is_auto(*trait_def_id))
511516
.collect(),
512517
render_options,
518+
type_trait_cache: RefCell::new(FxHashMap::default()),
513519
};
514520
debug!("crate: {:?}", tcx.hir().krate());
515521

src/librustdoc/passes/collect_intra_doc_links.rs

+59-45
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use rustc_ast as ast;
2+
use rustc_data_structures::stable_set::FxHashSet;
23
use rustc_errors::{Applicability, DiagnosticBuilder};
34
use rustc_expand::base::SyntaxExtensionKind;
45
use rustc_feature::UnstableFeatures;
@@ -9,7 +10,7 @@ use rustc_hir::def::{
910
PerNS, Res,
1011
};
1112
use rustc_hir::def_id::DefId;
12-
use rustc_middle::ty;
13+
use rustc_middle::ty::{self, TyCtxt};
1314
use rustc_resolve::ParentScope;
1415
use rustc_session::lint;
1516
use rustc_span::hygiene::MacroKind;
@@ -347,7 +348,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
347348
// HACK(jynelson): `clean` expects the type, not the associated item.
348349
// but the disambiguator logic expects the associated item.
349350
// Store the kind in a side channel so that only the disambiguator logic looks at it.
350-
self.kind_side_channel.replace(Some(item.kind.as_def_kind()));
351+
self.kind_side_channel.set(Some(kind.as_def_kind()));
351352
Ok((ty_res, Some(format!("{}.{}", out, item_name))))
352353
})
353354
} else if ns == Namespace::ValueNS {
@@ -443,8 +444,6 @@ fn resolve_associated_trait_item(
443444
ns: Namespace,
444445
cx: &DocContext<'_>,
445446
) -> Option<ty::AssocKind> {
446-
use rustc_hir::def_id::LOCAL_CRATE;
447-
448447
let ty = cx.tcx.type_of(did);
449448
// First consider automatic impls: `impl From<T> for T`
450449
let implicit_impls = crate::clean::get_auto_trait_and_blanket_impls(cx, ty, did);
@@ -501,54 +500,69 @@ fn resolve_associated_trait_item(
501500
}
502501
})
503502
.collect();
503+
504504
// Next consider explicit impls: `impl MyTrait for MyType`
505-
// There isn't a cheap way to do this. Just look at every trait!
506-
for &trait_ in cx.tcx.all_traits(LOCAL_CRATE) {
507-
trace!("considering explicit impl for trait {:?}", trait_);
508-
// We can skip the trait if it doesn't have the associated item `item_name`
509-
let assoc_item = cx
510-
.tcx
511-
.associated_items(trait_)
512-
.find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_)
513-
.map(|assoc| (assoc.def_id, assoc.kind));
514-
if let Some(assoc_item) = assoc_item {
515-
debug!("considering item {:?}", assoc_item);
516-
// Look at each trait implementation to see if it's an impl for `did`
517-
cx.tcx.for_each_relevant_impl(trait_, ty, |impl_| {
518-
use ty::TyKind;
519-
520-
let trait_ref = cx.tcx.impl_trait_ref(impl_).expect("this is not an inherent impl");
521-
// Check if these are the same type.
522-
let impl_type = trait_ref.self_ty();
523-
debug!(
524-
"comparing type {} with kind {:?} against def_id {:?}",
525-
impl_type, impl_type.kind, did
526-
);
527-
// Fast path: if this is a primitive simple `==` will work
528-
let same_type = impl_type == ty
529-
|| match impl_type.kind {
530-
// Check if these are the same def_id
531-
TyKind::Adt(def, _) => {
532-
debug!("adt did: {:?}", def.did);
533-
def.did == did
534-
}
535-
TyKind::Foreign(def_id) => def_id == did,
536-
_ => false,
537-
};
538-
if same_type {
539-
// We found it!
540-
debug!("found a match!");
541-
candidates.push(assoc_item);
542-
}
543-
});
544-
}
505+
// Give precedence to inherent impls.
506+
if candidates.is_empty() {
507+
let mut cache = cx.type_trait_cache.borrow_mut();
508+
let traits = cache.entry(did).or_insert_with(|| traits_implemented_by(cx.tcx, did));
509+
debug!("considering traits {:?}", traits);
510+
candidates.extend(traits.iter().filter_map(|&trait_| {
511+
cx.tcx
512+
.associated_items(trait_)
513+
.find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_)
514+
.map(|assoc| (assoc.def_id, assoc.kind))
515+
}));
545516
}
546-
547517
// FIXME: warn about ambiguity
548518
debug!("the candidates were {:?}", candidates);
549519
candidates.pop().map(|(_, kind)| kind)
550520
}
551521

522+
/// Given a type, return all traits implemented by that type.
523+
///
524+
/// NOTE: this cannot be a query because more traits could be available when more crates are compiled!
525+
/// So it is not stable to serialize cross-crate.
526+
/// FIXME: this should only search traits in scope
527+
fn traits_implemented_by<'a>(tcx: TyCtxt<'a>, type_: DefId) -> FxHashSet<DefId> {
528+
use rustc_hir::def_id::LOCAL_CRATE;
529+
530+
let all_traits = tcx.all_traits(LOCAL_CRATE).iter().copied();
531+
let ty = tcx.type_of(type_);
532+
let iter = all_traits.flat_map(|trait_| {
533+
trace!("considering explicit impl for trait {:?}", trait_);
534+
let mut saw_impl = false;
535+
// Look at each trait implementation to see if it's an impl for `did`
536+
tcx.for_each_relevant_impl(trait_, ty, |impl_| {
537+
// FIXME: this is inefficient, find a way to short-circuit for_each_* so this doesn't take as long
538+
if saw_impl {
539+
return;
540+
}
541+
542+
let trait_ref = tcx.impl_trait_ref(impl_).expect("this is not an inherent impl");
543+
// Check if these are the same type.
544+
let impl_type = trait_ref.self_ty();
545+
debug!(
546+
"comparing type {} with kind {:?} against type {:?}",
547+
impl_type, impl_type.kind, type_
548+
);
549+
// Fast path: if this is a primitive simple `==` will work
550+
saw_impl = impl_type == ty
551+
|| match impl_type.kind {
552+
// Check if these are the same def_id
553+
ty::Adt(def, _) => {
554+
debug!("adt def_id: {:?}", def.did);
555+
def.did == type_
556+
}
557+
ty::Foreign(def_id) => def_id == type_,
558+
_ => false,
559+
};
560+
});
561+
if saw_impl { Some(trait_) } else { None }
562+
});
563+
iter.collect()
564+
}
565+
552566
/// Check for resolve collisions between a trait and its derive
553567
///
554568
/// These are common and we should just resolve to the trait in that case

0 commit comments

Comments
 (0)