Skip to content

Commit 6874f4e

Browse files
committed
Auto merge of #107054 - petrochenkov:effvisdoc3, r=GuillaumeGomez
rustdoc: Collect "rustdoc-reachable" items during early doc link resolution This pass only needs to know about visibilities, attributes and reexports, so it can be run early, similarly to `compute_effective_visibilities` in rustc. Results of this pass can be used to prune the list of extern impls early thus improving performance of #94857.
2 parents 18890f0 + 3982258 commit 6874f4e

File tree

5 files changed

+93
-14
lines changed

5 files changed

+93
-14
lines changed

compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs

+6
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,12 @@ impl CStore {
632632
.get_attr_flags(def_id.index)
633633
.contains(AttrFlags::MAY_HAVE_DOC_LINKS)
634634
}
635+
636+
pub fn is_doc_hidden_untracked(&self, def_id: DefId) -> bool {
637+
self.get_crate_data(def_id.krate)
638+
.get_attr_flags(def_id.index)
639+
.contains(AttrFlags::IS_DOC_HIDDEN)
640+
}
635641
}
636642

637643
impl CrateStore for CStore {

src/librustdoc/clean/utils.rs

-5
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ mod tests;
2929
pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
3030
let module = crate::visit_ast::RustdocVisitor::new(cx).visit();
3131

32-
for &cnum in cx.tcx.crates(()) {
33-
// Analyze doc-reachability for extern items
34-
crate::visit_lib::lib_embargo_visit_item(cx, cnum.as_def_id());
35-
}
36-
3732
// Clean the crate, translating the entire librustc_ast AST to one that is
3833
// understood by rustdoc.
3934
let mut module = clean_doc_module(&module, cx);

src/librustdoc/core.rs

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub(crate) struct ResolverCaches {
4141
pub(crate) traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
4242
pub(crate) all_trait_impls: Option<Vec<DefId>>,
4343
pub(crate) all_macro_rules: FxHashMap<Symbol, Res<NodeId>>,
44+
pub(crate) extern_doc_reachable: DefIdSet,
4445
}
4546

4647
pub(crate) struct DocContext<'tcx> {
@@ -363,6 +364,10 @@ pub(crate) fn run_global_ctxt(
363364
show_coverage,
364365
};
365366

367+
ctxt.cache
368+
.effective_visibilities
369+
.init(mem::take(&mut ctxt.resolver_caches.extern_doc_reachable));
370+
366371
// Small hack to force the Sized trait to be present.
367372
//
368373
// Note that in case of `#![no_core]`, the trait is not available.

src/librustdoc/passes/collect_intra_doc_links/early.rs

+32-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::clean::Attributes;
22
use crate::core::ResolverCaches;
33
use crate::passes::collect_intra_doc_links::preprocessed_markdown_links;
44
use crate::passes::collect_intra_doc_links::{Disambiguator, PreprocessedMarkdownLink};
5+
use crate::visit_lib::early_lib_embargo_visit_item;
56

67
use rustc_ast::visit::{self, AssocCtxt, Visitor};
78
use rustc_ast::{self as ast, ItemKind};
@@ -34,6 +35,8 @@ pub(crate) fn early_resolve_intra_doc_links(
3435
traits_in_scope: Default::default(),
3536
all_trait_impls: Default::default(),
3637
all_macro_rules: Default::default(),
38+
extern_doc_reachable: Default::default(),
39+
local_doc_reachable: Default::default(),
3740
document_private_items,
3841
};
3942

@@ -61,6 +64,7 @@ pub(crate) fn early_resolve_intra_doc_links(
6164
traits_in_scope: link_resolver.traits_in_scope,
6265
all_trait_impls: Some(link_resolver.all_trait_impls),
6366
all_macro_rules: link_resolver.all_macro_rules,
67+
extern_doc_reachable: link_resolver.extern_doc_reachable,
6468
}
6569
}
6670

@@ -77,6 +81,15 @@ struct EarlyDocLinkResolver<'r, 'ra> {
7781
traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
7882
all_trait_impls: Vec<DefId>,
7983
all_macro_rules: FxHashMap<Symbol, Res<ast::NodeId>>,
84+
/// This set is used as a seed for `effective_visibilities`, which are then extended by some
85+
/// more items using `lib_embargo_visit_item` during doc inlining.
86+
extern_doc_reachable: DefIdSet,
87+
/// This is an easily identifiable superset of items added to `effective_visibilities`
88+
/// using `lib_embargo_visit_item` during doc inlining.
89+
/// The union of `(extern,local)_doc_reachable` is therefore a superset of
90+
/// `effective_visibilities` and can be used for pruning extern impls here
91+
/// in early doc link resolution.
92+
local_doc_reachable: DefIdSet,
8093
document_private_items: bool,
8194
}
8295

@@ -105,6 +118,10 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
105118
}
106119
}
107120

121+
fn is_doc_reachable(&self, def_id: DefId) -> bool {
122+
self.extern_doc_reachable.contains(&def_id) || self.local_doc_reachable.contains(&def_id)
123+
}
124+
108125
/// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass.
109126
/// That pass filters impls using type-based information, but we don't yet have such
110127
/// information here, so we just conservatively calculate traits in scope for *all* modules
@@ -114,6 +131,14 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
114131
let mut start_cnum = 0;
115132
loop {
116133
let crates = Vec::from_iter(self.resolver.cstore().crates_untracked());
134+
for cnum in &crates[start_cnum..] {
135+
early_lib_embargo_visit_item(
136+
self.resolver,
137+
&mut self.extern_doc_reachable,
138+
cnum.as_def_id(),
139+
true,
140+
);
141+
}
117142
for &cnum in &crates[start_cnum..] {
118143
let all_trait_impls =
119144
Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
@@ -127,28 +152,26 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
127152
// privacy, private traits and impls from other crates are never documented in
128153
// the current crate, and links in their doc comments are not resolved.
129154
for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
130-
if self.resolver.cstore().visibility_untracked(trait_def_id).is_public()
131-
&& simplified_self_ty.and_then(|ty| ty.def()).map_or(true, |ty_def_id| {
132-
self.resolver.cstore().visibility_untracked(ty_def_id).is_public()
133-
})
155+
if self.is_doc_reachable(trait_def_id)
156+
&& simplified_self_ty
157+
.and_then(|ty| ty.def())
158+
.map_or(true, |ty_def_id| self.is_doc_reachable(ty_def_id))
134159
{
135160
if self.visited_mods.insert(trait_def_id) {
136161
self.resolve_doc_links_extern_impl(trait_def_id, false);
137162
}
138163
self.resolve_doc_links_extern_impl(impl_def_id, false);
139164
}
165+
self.all_trait_impls.push(impl_def_id);
140166
}
141167
for (ty_def_id, impl_def_id) in all_inherent_impls {
142-
if self.resolver.cstore().visibility_untracked(ty_def_id).is_public() {
168+
if self.is_doc_reachable(ty_def_id) {
143169
self.resolve_doc_links_extern_impl(impl_def_id, true);
144170
}
145171
}
146172
for impl_def_id in all_incoherent_impls {
147173
self.resolve_doc_links_extern_impl(impl_def_id, true);
148174
}
149-
150-
self.all_trait_impls
151-
.extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id));
152175
}
153176

154177
if crates.len() > start_cnum {
@@ -298,6 +321,7 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
298321
&& module_id.is_local()
299322
{
300323
if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() {
324+
self.local_doc_reachable.insert(def_id);
301325
let scope_id = match child.res {
302326
Res::Def(
303327
DefKind::Variant

src/librustdoc/visit_lib.rs

+50-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::core::DocContext;
2-
use rustc_hir::def::DefKind;
2+
use rustc_hir::def::{DefKind, Res};
33
use rustc_hir::def_id::{DefId, DefIdSet};
44
use rustc_middle::ty::TyCtxt;
5+
use rustc_resolve::Resolver;
56

67
// FIXME: this may not be exhaustive, but is sufficient for rustdocs current uses
78

@@ -25,6 +26,10 @@ impl RustdocEffectiveVisibilities {
2526
define_method!(is_directly_public);
2627
define_method!(is_exported);
2728
define_method!(is_reachable);
29+
30+
pub(crate) fn init(&mut self, extern_public: DefIdSet) {
31+
self.extern_public = extern_public;
32+
}
2833
}
2934

3035
pub(crate) fn lib_embargo_visit_item(cx: &mut DocContext<'_>, def_id: DefId) {
@@ -37,6 +42,17 @@ pub(crate) fn lib_embargo_visit_item(cx: &mut DocContext<'_>, def_id: DefId) {
3742
.visit_item(def_id)
3843
}
3944

45+
pub(crate) fn early_lib_embargo_visit_item(
46+
resolver: &Resolver<'_>,
47+
extern_public: &mut DefIdSet,
48+
def_id: DefId,
49+
is_mod: bool,
50+
) {
51+
assert!(!def_id.is_local());
52+
EarlyLibEmbargoVisitor { resolver, extern_public, visited_mods: Default::default() }
53+
.visit_item(def_id, is_mod)
54+
}
55+
4056
/// Similar to `librustc_privacy::EmbargoVisitor`, but also takes
4157
/// specific rustdoc annotations into account (i.e., `doc(hidden)`)
4258
struct LibEmbargoVisitor<'a, 'tcx> {
@@ -47,6 +63,14 @@ struct LibEmbargoVisitor<'a, 'tcx> {
4763
visited_mods: DefIdSet,
4864
}
4965

66+
struct EarlyLibEmbargoVisitor<'r, 'ra> {
67+
resolver: &'r Resolver<'ra>,
68+
// Effective visibilities for reachable nodes
69+
extern_public: &'r mut DefIdSet,
70+
// Keeps track of already visited modules, in case a module re-exports its parent
71+
visited_mods: DefIdSet,
72+
}
73+
5074
impl LibEmbargoVisitor<'_, '_> {
5175
fn visit_mod(&mut self, def_id: DefId) {
5276
if !self.visited_mods.insert(def_id) {
@@ -71,3 +95,28 @@ impl LibEmbargoVisitor<'_, '_> {
7195
}
7296
}
7397
}
98+
99+
impl EarlyLibEmbargoVisitor<'_, '_> {
100+
fn visit_mod(&mut self, def_id: DefId) {
101+
if !self.visited_mods.insert(def_id) {
102+
return;
103+
}
104+
105+
for item in self.resolver.cstore().module_children_untracked(def_id, self.resolver.sess()) {
106+
if let Some(def_id) = item.res.opt_def_id() {
107+
if item.vis.is_public() {
108+
self.visit_item(def_id, matches!(item.res, Res::Def(DefKind::Mod, _)));
109+
}
110+
}
111+
}
112+
}
113+
114+
fn visit_item(&mut self, def_id: DefId, is_mod: bool) {
115+
if !self.resolver.cstore().is_doc_hidden_untracked(def_id) {
116+
self.extern_public.insert(def_id);
117+
if is_mod {
118+
self.visit_mod(def_id);
119+
}
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)