Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustdoc: Collect "rustdoc-reachable" items during early doc link resolution #107054

Merged
merged 3 commits into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,12 @@ impl CStore {
.get_attr_flags(def_id.index)
.contains(AttrFlags::MAY_HAVE_DOC_LINKS)
}

pub fn is_doc_hidden_untracked(&self, def_id: DefId) -> bool {
self.get_crate_data(def_id.krate)
.get_attr_flags(def_id.index)
.contains(AttrFlags::IS_DOC_HIDDEN)
}
}

impl CrateStore for CStore {
Expand Down
5 changes: 0 additions & 5 deletions src/librustdoc/clean/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ mod tests;
pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
let module = crate::visit_ast::RustdocVisitor::new(cx).visit();

for &cnum in cx.tcx.crates(()) {
// Analyze doc-reachability for extern items
crate::visit_lib::lib_embargo_visit_item(cx, cnum.as_def_id());
}

// Clean the crate, translating the entire librustc_ast AST to one that is
// understood by rustdoc.
let mut module = clean_doc_module(&module, cx);
Expand Down
5 changes: 5 additions & 0 deletions src/librustdoc/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub(crate) struct ResolverCaches {
pub(crate) traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
pub(crate) all_trait_impls: Option<Vec<DefId>>,
pub(crate) all_macro_rules: FxHashMap<Symbol, Res<NodeId>>,
pub(crate) extern_doc_reachable: DefIdSet,
}

pub(crate) struct DocContext<'tcx> {
Expand Down Expand Up @@ -363,6 +364,10 @@ pub(crate) fn run_global_ctxt(
show_coverage,
};

ctxt.cache
.effective_visibilities
.init(mem::take(&mut ctxt.resolver_caches.extern_doc_reachable));

// Small hack to force the Sized trait to be present.
//
// Note that in case of `#![no_core]`, the trait is not available.
Expand Down
40 changes: 32 additions & 8 deletions src/librustdoc/passes/collect_intra_doc_links/early.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::clean::Attributes;
use crate::core::ResolverCaches;
use crate::passes::collect_intra_doc_links::preprocessed_markdown_links;
use crate::passes::collect_intra_doc_links::{Disambiguator, PreprocessedMarkdownLink};
use crate::visit_lib::early_lib_embargo_visit_item;

use rustc_ast::visit::{self, AssocCtxt, Visitor};
use rustc_ast::{self as ast, ItemKind};
Expand Down Expand Up @@ -34,6 +35,8 @@ pub(crate) fn early_resolve_intra_doc_links(
traits_in_scope: Default::default(),
all_trait_impls: Default::default(),
all_macro_rules: Default::default(),
extern_doc_reachable: Default::default(),
local_doc_reachable: Default::default(),
document_private_items,
};

Expand Down Expand Up @@ -61,6 +64,7 @@ pub(crate) fn early_resolve_intra_doc_links(
traits_in_scope: link_resolver.traits_in_scope,
all_trait_impls: Some(link_resolver.all_trait_impls),
all_macro_rules: link_resolver.all_macro_rules,
extern_doc_reachable: link_resolver.extern_doc_reachable,
}
}

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

Expand Down Expand Up @@ -105,6 +118,10 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
}
}

fn is_doc_reachable(&self, def_id: DefId) -> bool {
self.extern_doc_reachable.contains(&def_id) || self.local_doc_reachable.contains(&def_id)
}

/// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass.
/// That pass filters impls using type-based information, but we don't yet have such
/// information here, so we just conservatively calculate traits in scope for *all* modules
Expand All @@ -114,6 +131,14 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
let mut start_cnum = 0;
loop {
let crates = Vec::from_iter(self.resolver.cstore().crates_untracked());
for cnum in &crates[start_cnum..] {
early_lib_embargo_visit_item(
self.resolver,
&mut self.extern_doc_reachable,
cnum.as_def_id(),
true,
);
}
for &cnum in &crates[start_cnum..] {
let all_trait_impls =
Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
Expand All @@ -127,28 +152,26 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
// privacy, private traits and impls from other crates are never documented in
// the current crate, and links in their doc comments are not resolved.
for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
if self.resolver.cstore().visibility_untracked(trait_def_id).is_public()
&& simplified_self_ty.and_then(|ty| ty.def()).map_or(true, |ty_def_id| {
self.resolver.cstore().visibility_untracked(ty_def_id).is_public()
})
if self.is_doc_reachable(trait_def_id)
&& simplified_self_ty
.and_then(|ty| ty.def())
.map_or(true, |ty_def_id| self.is_doc_reachable(ty_def_id))
{
if self.visited_mods.insert(trait_def_id) {
self.resolve_doc_links_extern_impl(trait_def_id, false);
}
self.resolve_doc_links_extern_impl(impl_def_id, false);
}
self.all_trait_impls.push(impl_def_id);
}
for (ty_def_id, impl_def_id) in all_inherent_impls {
if self.resolver.cstore().visibility_untracked(ty_def_id).is_public() {
if self.is_doc_reachable(ty_def_id) {
self.resolve_doc_links_extern_impl(impl_def_id, true);
}
}
for impl_def_id in all_incoherent_impls {
self.resolve_doc_links_extern_impl(impl_def_id, true);
}

self.all_trait_impls
.extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id));
}

if crates.len() > start_cnum {
Expand Down Expand Up @@ -298,6 +321,7 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
&& module_id.is_local()
{
if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() {
self.local_doc_reachable.insert(def_id);
let scope_id = match child.res {
Res::Def(
DefKind::Variant
Expand Down
51 changes: 50 additions & 1 deletion src/librustdoc/visit_lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::core::DocContext;
use rustc_hir::def::DefKind;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_middle::ty::TyCtxt;
use rustc_resolve::Resolver;

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

Expand All @@ -25,6 +26,10 @@ impl RustdocEffectiveVisibilities {
define_method!(is_directly_public);
define_method!(is_exported);
define_method!(is_reachable);

pub(crate) fn init(&mut self, extern_public: DefIdSet) {
self.extern_public = extern_public;
}
}

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

pub(crate) fn early_lib_embargo_visit_item(
resolver: &Resolver<'_>,
extern_public: &mut DefIdSet,
def_id: DefId,
is_mod: bool,
) {
assert!(!def_id.is_local());
EarlyLibEmbargoVisitor { resolver, extern_public, visited_mods: Default::default() }
.visit_item(def_id, is_mod)
}

/// Similar to `librustc_privacy::EmbargoVisitor`, but also takes
/// specific rustdoc annotations into account (i.e., `doc(hidden)`)
struct LibEmbargoVisitor<'a, 'tcx> {
Expand All @@ -47,6 +63,14 @@ struct LibEmbargoVisitor<'a, 'tcx> {
visited_mods: DefIdSet,
}

struct EarlyLibEmbargoVisitor<'r, 'ra> {
resolver: &'r Resolver<'ra>,
// Effective visibilities for reachable nodes
extern_public: &'r mut DefIdSet,
// Keeps track of already visited modules, in case a module re-exports its parent
visited_mods: DefIdSet,
}

impl LibEmbargoVisitor<'_, '_> {
fn visit_mod(&mut self, def_id: DefId) {
if !self.visited_mods.insert(def_id) {
Expand All @@ -71,3 +95,28 @@ impl LibEmbargoVisitor<'_, '_> {
}
}
}

impl EarlyLibEmbargoVisitor<'_, '_> {
fn visit_mod(&mut self, def_id: DefId) {
if !self.visited_mods.insert(def_id) {
return;
}

for item in self.resolver.cstore().module_children_untracked(def_id, self.resolver.sess()) {
if let Some(def_id) = item.res.opt_def_id() {
if item.vis.is_public() {
self.visit_item(def_id, matches!(item.res, Res::Def(DefKind::Mod, _)));
}
}
}
}

fn visit_item(&mut self, def_id: DefId, is_mod: bool) {
if !self.resolver.cstore().is_doc_hidden_untracked(def_id) {
self.extern_public.insert(def_id);
if is_mod {
self.visit_mod(def_id);
}
}
}
}