Skip to content

Commit 788b1fe

Browse files
committed
Auto merge of #88679 - petrochenkov:doctrscope, r=GuillaumeGomez
rustdoc: Pre-calculate traits that are in scope for doc links This eliminates one more late use of resolver (part of #83761). At early doc link resolution time we go through parent modules of items from the current crate, reexports of items from other crates, trait items, and impl items collected by `collect-intra-doc-links` pass, determine traits that are in scope in each such module, and put those traits into a map used by later rustdoc passes. r? `@jyn514`
2 parents d502eda + 00ba815 commit 788b1fe

File tree

7 files changed

+131
-41
lines changed

7 files changed

+131
-41
lines changed

compiler/rustc_metadata/src/rmeta/decoder.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -1373,11 +1373,15 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
13731373
self.root.traits.decode(self).map(move |index| self.local_def_id(index))
13741374
}
13751375

1376-
fn get_trait_impls(self) -> impl Iterator<Item = (DefId, Option<SimplifiedType>)> + 'a {
1377-
self.cdata.trait_impls.values().flat_map(move |impls| {
1378-
impls
1379-
.decode(self)
1380-
.map(move |(idx, simplified_self_ty)| (self.local_def_id(idx), simplified_self_ty))
1376+
fn get_trait_impls(self) -> impl Iterator<Item = (DefId, DefId, Option<SimplifiedType>)> + 'a {
1377+
self.cdata.trait_impls.iter().flat_map(move |((trait_cnum_raw, trait_index), impls)| {
1378+
let trait_def_id = DefId {
1379+
krate: self.cnum_map[CrateNum::from_u32(*trait_cnum_raw)],
1380+
index: *trait_index,
1381+
};
1382+
impls.decode(self).map(move |(impl_index, simplified_self_ty)| {
1383+
(trait_def_id, self.local_def_id(impl_index), simplified_self_ty)
1384+
})
13811385
})
13821386
}
13831387

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ impl CStore {
493493
pub fn trait_impls_in_crate_untracked(
494494
&self,
495495
cnum: CrateNum,
496-
) -> impl Iterator<Item = (DefId, Option<SimplifiedType>)> + '_ {
496+
) -> impl Iterator<Item = (DefId, DefId, Option<SimplifiedType>)> + '_ {
497497
self.get_crate_data(cnum).get_trait_impls()
498498
}
499499
}

compiler/rustc_resolve/src/build_reduced_graph.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl<'a> Resolver<'a> {
108108
/// Reachable macros with block module parents exist due to `#[macro_export] macro_rules!`,
109109
/// but they cannot use def-site hygiene, so the assumption holds
110110
/// (<https://github.com/rust-lang/rust/pull/77984#issuecomment-712445508>).
111-
crate fn get_nearest_non_block_module(&mut self, mut def_id: DefId) -> Module<'a> {
111+
pub fn get_nearest_non_block_module(&mut self, mut def_id: DefId) -> Module<'a> {
112112
loop {
113113
match self.get_module(def_id) {
114114
Some(module) => return module,

compiler/rustc_resolve/src/lib.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,8 @@ impl<'a> ModuleData<'a> {
614614
}
615615
}
616616

617-
fn def_id(&self) -> DefId {
617+
// Public for rustdoc.
618+
pub fn def_id(&self) -> DefId {
618619
self.opt_def_id().expect("`ModuleData::def_id` is called on a block module")
619620
}
620621

@@ -3407,6 +3408,16 @@ impl<'a> Resolver<'a> {
34073408
&self.all_macros
34083409
}
34093410

3411+
/// For rustdoc.
3412+
/// For local modules returns only reexports, for external modules returns all children.
3413+
pub fn module_children_or_reexports(&self, def_id: DefId) -> Vec<ModChild> {
3414+
if let Some(def_id) = def_id.as_local() {
3415+
self.reexport_map.get(&def_id).cloned().unwrap_or_default()
3416+
} else {
3417+
self.cstore().module_children_untracked(def_id, self.session)
3418+
}
3419+
}
3420+
34103421
/// Retrieves the span of the given `DefId` if `DefId` is in the local crate.
34113422
#[inline]
34123423
pub fn opt_span(&self, def_id: DefId) -> Option<Span> {

src/librustdoc/core.rs

+5-8
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use rustc_errors::emitter::{Emitter, EmitterWriter};
44
use rustc_errors::json::JsonEmitter;
55
use rustc_feature::UnstableFeatures;
66
use rustc_hir::def::Res;
7-
use rustc_hir::def_id::{DefId, LocalDefId};
7+
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId};
88
use rustc_hir::intravisit::{self, Visitor};
9-
use rustc_hir::{HirId, Path};
9+
use rustc_hir::{HirId, Path, TraitCandidate};
1010
use rustc_interface::interface;
1111
use rustc_middle::hir::nested_filter;
1212
use rustc_middle::middle::privacy::AccessLevels;
@@ -33,6 +33,9 @@ use crate::passes::{self, Condition::*};
3333
crate use rustc_session::config::{DebuggingOptions, Input, Options};
3434

3535
crate struct ResolverCaches {
36+
/// Traits in scope for a given module.
37+
/// See `collect_intra_doc_links::traits_implemented_by` for more details.
38+
crate traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
3639
crate all_traits: Option<Vec<DefId>>,
3740
crate all_trait_impls: Option<Vec<DefId>>,
3841
}
@@ -67,11 +70,6 @@ crate struct DocContext<'tcx> {
6770
crate auto_traits: Vec<DefId>,
6871
/// The options given to rustdoc that could be relevant to a pass.
6972
crate render_options: RenderOptions,
70-
/// The traits in scope for a given module.
71-
///
72-
/// See `collect_intra_doc_links::traits_implemented_by` for more details.
73-
/// `map<module, set<trait>>`
74-
crate module_trait_cache: FxHashMap<DefId, FxHashSet<DefId>>,
7573
/// This same cache is used throughout rustdoc, including in [`crate::html::render`].
7674
crate cache: Cache,
7775
/// Used by [`clean::inline`] to tell if an item has already been inlined.
@@ -350,7 +348,6 @@ crate fn run_global_ctxt(
350348
impl_trait_bounds: Default::default(),
351349
generated_synthetics: Default::default(),
352350
auto_traits,
353-
module_trait_cache: FxHashMap::default(),
354351
cache: Cache::new(access_levels, render_options.document_private),
355352
inlined: FxHashSet::default(),
356353
output_format,

src/librustdoc/passes/collect_intra_doc_links.rs

+3-14
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_middle::ty::{DefIdTree, Ty, TyCtxt};
1616
use rustc_middle::{bug, span_bug, ty};
1717
use rustc_resolve::ParentScope;
1818
use rustc_session::lint::Lint;
19-
use rustc_span::hygiene::{MacroKind, SyntaxContext};
19+
use rustc_span::hygiene::MacroKind;
2020
use rustc_span::symbol::{sym, Ident, Symbol};
2121
use rustc_span::{BytePos, DUMMY_SP};
2222
use smallvec::{smallvec, SmallVec};
@@ -925,20 +925,9 @@ fn trait_impls_for<'a>(
925925
ty: Ty<'a>,
926926
module: DefId,
927927
) -> FxHashSet<(DefId, DefId)> {
928-
let mut resolver = cx.resolver.borrow_mut();
929-
let in_scope_traits = cx.module_trait_cache.entry(module).or_insert_with(|| {
930-
resolver.access(|resolver| {
931-
let parent_scope = &ParentScope::module(resolver.expect_module(module), resolver);
932-
resolver
933-
.traits_in_scope(None, parent_scope, SyntaxContext::root(), None)
934-
.into_iter()
935-
.map(|candidate| candidate.def_id)
936-
.collect()
937-
})
938-
});
939-
940928
let tcx = cx.tcx;
941-
let iter = in_scope_traits.iter().flat_map(|&trait_| {
929+
let iter = cx.resolver_caches.traits_in_scope[&module].iter().flat_map(|trait_candidate| {
930+
let trait_ = trait_candidate.def_id;
942931
trace!("considering explicit impl for trait {:?}", trait_);
943932

944933
// Look at each trait implementation to see if it's an impl for `did`

src/librustdoc/passes/collect_intra_doc_links/early.rs

+100-11
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ use rustc_ast::visit::{self, AssocCtxt, Visitor};
77
use rustc_ast::{self as ast, ItemKind};
88
use rustc_ast_lowering::ResolverAstLowering;
99
use rustc_hir::def::Namespace::TypeNS;
10-
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
11-
use rustc_resolve::Resolver;
10+
use rustc_hir::def::{DefKind, Res};
11+
use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId, CRATE_DEF_ID};
12+
use rustc_hir::TraitCandidate;
13+
use rustc_middle::ty::{DefIdTree, Visibility};
14+
use rustc_resolve::{ParentScope, Resolver};
1215
use rustc_session::config::Externs;
13-
use rustc_span::{Span, DUMMY_SP};
16+
use rustc_span::{Span, SyntaxContext, DUMMY_SP};
1417

18+
use std::collections::hash_map::Entry;
1519
use std::mem;
1620

1721
crate fn early_resolve_intra_doc_links(
@@ -22,15 +26,18 @@ crate fn early_resolve_intra_doc_links(
2226
let mut loader = IntraLinkCrateLoader {
2327
resolver,
2428
current_mod: CRATE_DEF_ID,
29+
visited_mods: Default::default(),
30+
traits_in_scope: Default::default(),
2531
all_traits: Default::default(),
2632
all_trait_impls: Default::default(),
2733
};
2834

2935
// Overridden `visit_item` below doesn't apply to the crate root,
30-
// so we have to visit its attributes and exports separately.
36+
// so we have to visit its attributes and reexports separately.
3137
loader.load_links_in_attrs(&krate.attrs, krate.span);
38+
loader.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id());
3239
visit::walk_crate(&mut loader, krate);
33-
loader.fill_resolver_caches();
40+
loader.add_foreign_traits_in_scope();
3441

3542
// FIXME: somehow rustdoc is still missing crates even though we loaded all
3643
// the known necessary crates. Load them all unconditionally until we find a way to fix this.
@@ -46,6 +53,7 @@ crate fn early_resolve_intra_doc_links(
4653
}
4754

4855
ResolverCaches {
56+
traits_in_scope: loader.traits_in_scope,
4957
all_traits: Some(loader.all_traits),
5058
all_trait_impls: Some(loader.all_trait_impls),
5159
}
@@ -54,27 +62,87 @@ crate fn early_resolve_intra_doc_links(
5462
struct IntraLinkCrateLoader<'r, 'ra> {
5563
resolver: &'r mut Resolver<'ra>,
5664
current_mod: LocalDefId,
65+
visited_mods: DefIdSet,
66+
traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
5767
all_traits: Vec<DefId>,
5868
all_trait_impls: Vec<DefId>,
5969
}
6070

6171
impl IntraLinkCrateLoader<'_, '_> {
62-
fn fill_resolver_caches(&mut self) {
63-
for cnum in self.resolver.cstore().crates_untracked() {
64-
let all_traits = self.resolver.cstore().traits_in_crate_untracked(cnum);
65-
let all_trait_impls = self.resolver.cstore().trait_impls_in_crate_untracked(cnum);
72+
fn add_traits_in_scope(&mut self, def_id: DefId) {
73+
// Calls to `traits_in_scope` are expensive, so try to avoid them if only possible.
74+
// Keys in the `traits_in_scope` cache are always module IDs.
75+
if let Entry::Vacant(entry) = self.traits_in_scope.entry(def_id) {
76+
let module = self.resolver.get_nearest_non_block_module(def_id);
77+
let module_id = module.def_id();
78+
let entry = if module_id == def_id {
79+
Some(entry)
80+
} else if let Entry::Vacant(entry) = self.traits_in_scope.entry(module_id) {
81+
Some(entry)
82+
} else {
83+
None
84+
};
85+
if let Some(entry) = entry {
86+
entry.insert(self.resolver.traits_in_scope(
87+
None,
88+
&ParentScope::module(module, self.resolver),
89+
SyntaxContext::root(),
90+
None,
91+
));
92+
}
93+
}
94+
}
95+
96+
fn add_traits_in_parent_scope(&mut self, def_id: DefId) {
97+
if let Some(module_id) = self.resolver.parent(def_id) {
98+
self.add_traits_in_scope(module_id);
99+
}
100+
}
101+
102+
/// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass.
103+
/// That pass filters impls using type-based information, but we don't yet have such
104+
/// information here, so we just conservatively calculate traits in scope for *all* modules
105+
/// having impls in them.
106+
fn add_foreign_traits_in_scope(&mut self) {
107+
for cnum in Vec::from_iter(self.resolver.cstore().crates_untracked()) {
108+
// FIXME: Due to #78696 rustdoc can query traits in scope for any crate root.
109+
self.add_traits_in_scope(cnum.as_def_id());
110+
111+
let all_traits = Vec::from_iter(self.resolver.cstore().traits_in_crate_untracked(cnum));
112+
let all_trait_impls =
113+
Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
114+
115+
// Querying traits in scope is expensive so we try to prune the impl and traits lists
116+
// using privacy, private traits and impls from other crates are never documented in
117+
// the current crate, and links in their doc comments are not resolved.
118+
for &def_id in &all_traits {
119+
if self.resolver.cstore().visibility_untracked(def_id) == Visibility::Public {
120+
self.add_traits_in_parent_scope(def_id);
121+
}
122+
}
123+
for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
124+
if self.resolver.cstore().visibility_untracked(trait_def_id) == Visibility::Public
125+
&& simplified_self_ty.and_then(|ty| ty.def()).map_or(true, |ty_def_id| {
126+
self.resolver.cstore().visibility_untracked(ty_def_id) == Visibility::Public
127+
})
128+
{
129+
self.add_traits_in_parent_scope(impl_def_id);
130+
}
131+
}
66132

67133
self.all_traits.extend(all_traits);
68-
self.all_trait_impls.extend(all_trait_impls.into_iter().map(|(def_id, _)| def_id));
134+
self.all_trait_impls.extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id));
69135
}
70136
}
71137

72138
fn load_links_in_attrs(&mut self, attrs: &[ast::Attribute], span: Span) {
73-
// FIXME: this needs to consider export inlining.
139+
// FIXME: this needs to consider reexport inlining.
74140
let attrs = clean::Attributes::from_ast(attrs, None);
75141
for (parent_module, doc) in attrs.collapsed_doc_value_by_module_level() {
76142
let module_id = parent_module.unwrap_or(self.current_mod.to_def_id());
77143

144+
self.add_traits_in_scope(module_id);
145+
78146
for link in markdown_links(&doc.as_str()) {
79147
let path_str = if let Some(Ok(x)) = preprocess_link(&link) {
80148
x.path_str
@@ -85,6 +153,26 @@ impl IntraLinkCrateLoader<'_, '_> {
85153
}
86154
}
87155
}
156+
157+
/// When reexports are inlined, they are replaced with item which they refer to, those items
158+
/// may have links in their doc comments, those links are resolved at the item definition site,
159+
/// so we need to know traits in scope at that definition site.
160+
fn process_module_children_or_reexports(&mut self, module_id: DefId) {
161+
if !self.visited_mods.insert(module_id) {
162+
return; // avoid infinite recursion
163+
}
164+
165+
for child in self.resolver.module_children_or_reexports(module_id) {
166+
if child.vis == Visibility::Public {
167+
if let Some(def_id) = child.res.opt_def_id() {
168+
self.add_traits_in_parent_scope(def_id);
169+
}
170+
if let Res::Def(DefKind::Mod, module_id) = child.res {
171+
self.process_module_children_or_reexports(module_id);
172+
}
173+
}
174+
}
175+
}
88176
}
89177

90178
impl Visitor<'_> for IntraLinkCrateLoader<'_, '_> {
@@ -93,6 +181,7 @@ impl Visitor<'_> for IntraLinkCrateLoader<'_, '_> {
93181
let old_mod = mem::replace(&mut self.current_mod, self.resolver.local_def_id(item.id));
94182

95183
self.load_links_in_attrs(&item.attrs, item.span);
184+
self.process_module_children_or_reexports(self.current_mod.to_def_id());
96185
visit::walk_item(self, item);
97186

98187
self.current_mod = old_mod;

0 commit comments

Comments
 (0)