Skip to content

Commit 72ed101

Browse files
committed
rustdoc: Optimize and refactor doc link resolution
- Cache doc link resolutions obtained early - Cache markdown links retrieved from doc strings early - Rename and restructure the code in early doc link resolution to be closer to #94857
1 parent e2d3a4f commit 72ed101

File tree

4 files changed

+112
-40
lines changed

4 files changed

+112
-40
lines changed

src/librustdoc/clean/types.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,13 @@ impl Attributes {
10881088
crate fn from_ast(
10891089
attrs: &[ast::Attribute],
10901090
additional_attrs: Option<(&[ast::Attribute], DefId)>,
1091+
) -> Attributes {
1092+
Attributes::from_ast_iter(attrs.iter(), additional_attrs)
1093+
}
1094+
1095+
crate fn from_ast_iter<'a>(
1096+
attrs: impl Iterator<Item = &'a ast::Attribute>,
1097+
additional_attrs: Option<(&[ast::Attribute], DefId)>,
10911098
) -> Attributes {
10921099
let mut doc_strings: Vec<DocFragment> = vec![];
10931100
let clean_attr = |(attr, parent_module): (&ast::Attribute, Option<DefId>)| {
@@ -1115,7 +1122,7 @@ impl Attributes {
11151122
let other_attrs = additional_attrs
11161123
.into_iter()
11171124
.flat_map(|(attrs, id)| attrs.iter().map(move |attr| (attr, Some(id))))
1118-
.chain(attrs.iter().map(|attr| (attr, None)))
1125+
.chain(attrs.map(|attr| (attr, None)))
11191126
.filter_map(clean_attr)
11201127
.collect();
11211128

src/librustdoc/core.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc_data_structures::sync::{self, Lrc};
44
use rustc_errors::emitter::{Emitter, EmitterWriter};
55
use rustc_errors::json::JsonEmitter;
66
use rustc_feature::UnstableFeatures;
7-
use rustc_hir::def::Res;
7+
use rustc_hir::def::{Namespace, Res};
88
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId};
99
use rustc_hir::intravisit::{self, Visitor};
1010
use rustc_hir::{HirId, Path, TraitCandidate};
@@ -29,11 +29,14 @@ use crate::clean::inline::build_external_trait;
2929
use crate::clean::{self, ItemId, TraitWithExtraInfo};
3030
use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
3131
use crate::formats::cache::Cache;
32+
use crate::html::markdown::MarkdownLink;
3233
use crate::passes::{self, Condition::*};
3334

3435
crate use rustc_session::config::{DebuggingOptions, Input, Options};
3536

3637
crate struct ResolverCaches {
38+
crate markdown_links: Option<FxHashMap<String, Vec<MarkdownLink>>>,
39+
crate doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<NodeId>>>,
3740
/// Traits in scope for a given module.
3841
/// See `collect_intra_doc_links::traits_implemented_by` for more details.
3942
crate traits_in_scope: DefIdMap<Vec<TraitCandidate>>,

src/librustdoc/passes/collect_intra_doc_links.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! [RFC 1946]: https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md
44
55
use pulldown_cmark::LinkType;
6+
use rustc_ast::util::comments::may_have_doc_links;
67
use rustc_data_structures::{fx::FxHashMap, intern::Interned, stable_set::FxHashSet};
78
use rustc_errors::{Applicability, Diagnostic};
89
use rustc_hir::def::Namespace::*;
@@ -556,7 +557,15 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
556557
// Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`).
557558
let result = self
558559
.cx
559-
.enter_resolver(|resolver| resolver.resolve_rustdoc_path(path_str, ns, module_id))
560+
.resolver_caches
561+
.doc_link_resolutions
562+
.get(&(Symbol::intern(path_str), ns, module_id))
563+
.copied()
564+
.unwrap_or_else(|| {
565+
self.cx.enter_resolver(|resolver| {
566+
resolver.resolve_rustdoc_path(path_str, ns, module_id)
567+
})
568+
})
560569
.and_then(|res| res.try_into().ok())
561570
.or_else(|| resolve_primitive(path_str, ns))
562571
.or_else(|| self.resolve_macro_rules(path_str, ns));
@@ -1041,16 +1050,29 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
10411050
// Rather than merging all documentation into one, resolve it one attribute at a time
10421051
// so we know which module it came from.
10431052
for (parent_module, doc) in item.attrs.collapsed_doc_value_by_module_level() {
1053+
if !may_have_doc_links(&doc) {
1054+
continue;
1055+
}
10441056
debug!("combined_docs={}", doc);
10451057
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
10461058
// This is a degenerate case and it's not supported by rustdoc.
10471059
let parent_node = parent_module.or(parent_node);
1048-
for md_link in markdown_links(&doc) {
1060+
let mut tmp_links = self
1061+
.cx
1062+
.resolver_caches
1063+
.markdown_links
1064+
.take()
1065+
.expect("`markdown_links` are already borrowed");
1066+
if !tmp_links.contains_key(&doc) {
1067+
tmp_links.insert(doc.clone(), markdown_links(&doc));
1068+
}
1069+
for md_link in &tmp_links[&doc] {
10491070
let link = self.resolve_link(&item, &doc, parent_node, md_link);
10501071
if let Some(link) = link {
10511072
self.cx.cache.intra_doc_links.entry(item.item_id).or_default().push(link);
10521073
}
10531074
}
1075+
self.cx.resolver_caches.markdown_links = Some(tmp_links);
10541076
}
10551077

10561078
if item.is_mod() {
@@ -1181,7 +1203,7 @@ impl LinkCollector<'_, '_> {
11811203
item: &Item,
11821204
dox: &str,
11831205
parent_node: Option<DefId>,
1184-
ori_link: MarkdownLink,
1206+
ori_link: &MarkdownLink,
11851207
) -> Option<ItemLink> {
11861208
trace!("considering link '{}'", ori_link.link);
11871209

@@ -1320,7 +1342,7 @@ impl LinkCollector<'_, '_> {
13201342
}
13211343

13221344
Some(ItemLink {
1323-
link: ori_link.link,
1345+
link: ori_link.link.clone(),
13241346
link_text,
13251347
did: res.def_id(self.cx.tcx),
13261348
fragment,
@@ -1343,7 +1365,7 @@ impl LinkCollector<'_, '_> {
13431365
&diag_info,
13441366
)?;
13451367
let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
1346-
Some(ItemLink { link: ori_link.link, link_text, did: id, fragment })
1368+
Some(ItemLink { link: ori_link.link.clone(), link_text, did: id, fragment })
13471369
}
13481370
}
13491371
}

src/librustdoc/passes/collect_intra_doc_links/early.rs

+73-33
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
use crate::clean::Attributes;
22
use crate::core::ResolverCaches;
3-
use crate::html::markdown::markdown_links;
3+
use crate::html::markdown::{markdown_links, MarkdownLink};
44
use crate::passes::collect_intra_doc_links::preprocess_link;
55

66
use rustc_ast::visit::{self, AssocCtxt, Visitor};
77
use rustc_ast::{self as ast, ItemKind};
88
use rustc_ast_lowering::ResolverAstLowering;
9-
use rustc_hir::def::Namespace::TypeNS;
10-
use rustc_hir::def::{DefKind, Res};
9+
use rustc_data_structures::fx::FxHashMap;
10+
use rustc_hir::def::Namespace::*;
11+
use rustc_hir::def::{DefKind, Namespace, Res};
1112
use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId, CRATE_DEF_ID};
1213
use rustc_hir::TraitCandidate;
1314
use rustc_middle::ty::{DefIdTree, Visibility};
1415
use rustc_resolve::{ParentScope, Resolver};
1516
use rustc_session::config::Externs;
16-
use rustc_span::SyntaxContext;
17+
use rustc_span::{Symbol, SyntaxContext};
1718

1819
use std::collections::hash_map::Entry;
1920
use std::mem;
@@ -28,6 +29,8 @@ crate fn early_resolve_intra_doc_links(
2829
resolver,
2930
current_mod: CRATE_DEF_ID,
3031
visited_mods: Default::default(),
32+
markdown_links: Default::default(),
33+
doc_link_resolutions: Default::default(),
3134
traits_in_scope: Default::default(),
3235
all_traits: Default::default(),
3336
all_trait_impls: Default::default(),
@@ -36,7 +39,7 @@ crate fn early_resolve_intra_doc_links(
3639

3740
// Overridden `visit_item` below doesn't apply to the crate root,
3841
// so we have to visit its attributes and reexports separately.
39-
link_resolver.load_links_in_attrs(&krate.attrs);
42+
link_resolver.resolve_doc_links_local(&krate.attrs);
4043
link_resolver.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id());
4144
visit::walk_crate(&mut link_resolver, krate);
4245
link_resolver.process_extern_impls();
@@ -50,17 +53,27 @@ crate fn early_resolve_intra_doc_links(
5053
}
5154

5255
ResolverCaches {
56+
markdown_links: Some(link_resolver.markdown_links),
57+
doc_link_resolutions: link_resolver.doc_link_resolutions,
5358
traits_in_scope: link_resolver.traits_in_scope,
5459
all_traits: Some(link_resolver.all_traits),
5560
all_trait_impls: Some(link_resolver.all_trait_impls),
5661
all_macro_rules: link_resolver.resolver.take_all_macro_rules(),
5762
}
5863
}
5964

65+
fn doc_attrs<'a>(attrs: impl Iterator<Item = &'a ast::Attribute>) -> Attributes {
66+
let mut attrs = Attributes::from_ast_iter(attrs.filter(|attr| attr.doc_str().is_some()), None);
67+
attrs.unindent_doc_comments();
68+
attrs
69+
}
70+
6071
struct EarlyDocLinkResolver<'r, 'ra> {
6172
resolver: &'r mut Resolver<'ra>,
6273
current_mod: LocalDefId,
6374
visited_mods: DefIdSet,
75+
markdown_links: FxHashMap<String, Vec<MarkdownLink>>,
76+
doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<ast::NodeId>>>,
6477
traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
6578
all_traits: Vec<DefId>,
6679
all_trait_impls: Vec<DefId>,
@@ -92,18 +105,11 @@ impl EarlyDocLinkResolver<'_, '_> {
92105
}
93106
}
94107

95-
fn add_traits_in_parent_scope(&mut self, def_id: DefId) {
96-
if let Some(module_id) = self.resolver.parent(def_id) {
97-
self.add_traits_in_scope(module_id);
98-
}
99-
}
100-
101108
/// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass.
102109
/// That pass filters impls using type-based information, but we don't yet have such
103110
/// information here, so we just conservatively calculate traits in scope for *all* modules
104111
/// having impls in them.
105112
fn process_extern_impls(&mut self) {
106-
// FIXME: Need to resolve doc links on all these impl and trait items below.
107113
// Resolving links in already existing crates may trigger loading of new crates.
108114
let mut start_cnum = 0;
109115
loop {
@@ -124,7 +130,7 @@ impl EarlyDocLinkResolver<'_, '_> {
124130
// the current crate, and links in their doc comments are not resolved.
125131
for &def_id in &all_traits {
126132
if self.resolver.cstore().visibility_untracked(def_id) == Visibility::Public {
127-
self.add_traits_in_parent_scope(def_id);
133+
self.resolve_doc_links_extern_impl(def_id, false);
128134
}
129135
}
130136
for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
@@ -135,17 +141,17 @@ impl EarlyDocLinkResolver<'_, '_> {
135141
== Visibility::Public
136142
})
137143
{
138-
self.add_traits_in_parent_scope(impl_def_id);
144+
self.resolve_doc_links_extern_impl(impl_def_id, false);
139145
}
140146
}
141147
for (ty_def_id, impl_def_id) in all_inherent_impls {
142148
if self.resolver.cstore().visibility_untracked(ty_def_id) == Visibility::Public
143149
{
144-
self.add_traits_in_parent_scope(impl_def_id);
150+
self.resolve_doc_links_extern_impl(impl_def_id, true);
145151
}
146152
}
147-
for def_id in all_incoherent_impls {
148-
self.add_traits_in_parent_scope(def_id);
153+
for impl_def_id in all_incoherent_impls {
154+
self.resolve_doc_links_extern_impl(impl_def_id, true);
149155
}
150156

151157
self.all_traits.extend(all_traits);
@@ -161,16 +167,52 @@ impl EarlyDocLinkResolver<'_, '_> {
161167
}
162168
}
163169

164-
fn load_links_in_attrs(&mut self, attrs: &[ast::Attribute]) {
170+
fn resolve_doc_links_extern_impl(&mut self, def_id: DefId, _is_inherent: bool) {
171+
// FIXME: Resolve links in associated items in addition to traits themselves,
172+
// `force` is used to provide traits in scope for the associated items.
173+
self.resolve_doc_links_extern_outer(def_id, def_id, true);
174+
}
175+
176+
fn resolve_doc_links_extern_outer(&mut self, def_id: DefId, scope_id: DefId, force: bool) {
177+
if !force && !self.resolver.cstore().may_have_doc_links_untracked(def_id) {
178+
return;
179+
}
180+
// FIXME: actually resolve links, not just add traits in scope.
181+
if let Some(parent_id) = self.resolver.parent(scope_id) {
182+
self.add_traits_in_scope(parent_id);
183+
}
184+
}
185+
186+
fn resolve_doc_links_extern_inner(&mut self, def_id: DefId) {
187+
if !self.resolver.cstore().may_have_doc_links_untracked(def_id) {
188+
return;
189+
}
190+
// FIXME: actually resolve links, not just add traits in scope.
191+
self.add_traits_in_scope(def_id);
192+
}
193+
194+
fn resolve_doc_links_local(&mut self, attrs: &[ast::Attribute]) {
195+
if !attrs.iter().any(|attr| attr.may_have_doc_links()) {
196+
return;
197+
}
165198
let module_id = self.current_mod.to_def_id();
199+
self.resolve_doc_links(doc_attrs(attrs.iter()), module_id);
200+
}
201+
202+
fn resolve_doc_links(&mut self, attrs: Attributes, module_id: DefId) {
166203
let mut need_traits_in_scope = false;
167-
for (doc_module, doc) in
168-
Attributes::from_ast(attrs, None).collapsed_doc_value_by_module_level()
169-
{
204+
for (doc_module, doc) in attrs.collapsed_doc_value_by_module_level() {
170205
assert_eq!(doc_module, None);
171-
for link in markdown_links(&doc.as_str()) {
206+
for link in self.markdown_links.entry(doc).or_insert_with_key(|doc| markdown_links(doc))
207+
{
172208
if let Some(Ok(pinfo)) = preprocess_link(&link) {
173-
self.resolver.resolve_rustdoc_path(&pinfo.path_str, TypeNS, module_id);
209+
// FIXME: Resolve the path in all namespaces and resolve its prefixes too.
210+
let ns = TypeNS;
211+
self.doc_link_resolutions
212+
.entry((Symbol::intern(&pinfo.path_str), ns, module_id))
213+
.or_insert_with_key(|(path, ns, module_id)| {
214+
self.resolver.resolve_rustdoc_path(path.as_str(), *ns, *module_id)
215+
});
174216
need_traits_in_scope = true;
175217
}
176218
}
@@ -197,15 +239,13 @@ impl EarlyDocLinkResolver<'_, '_> {
197239
&& module_id.is_local()
198240
{
199241
if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() {
200-
// FIXME: Need to resolve doc links on all these extern items
201-
// reached through reexports.
202242
let scope_id = match child.res {
203243
Res::Def(DefKind::Variant, ..) => self.resolver.parent(def_id).unwrap(),
204244
_ => def_id,
205245
};
206-
self.add_traits_in_parent_scope(scope_id); // Outer attribute scope
246+
self.resolve_doc_links_extern_outer(def_id, scope_id, false); // Outer attribute scope
207247
if let Res::Def(DefKind::Mod, ..) = child.res {
208-
self.add_traits_in_scope(def_id); // Inner attribute scope
248+
self.resolve_doc_links_extern_inner(def_id); // Inner attribute scope
209249
}
210250
// Traits are processed in `add_extern_traits_in_scope`.
211251
if let Res::Def(DefKind::Mod | DefKind::Enum, ..) = child.res {
@@ -219,10 +259,10 @@ impl EarlyDocLinkResolver<'_, '_> {
219259

220260
impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
221261
fn visit_item(&mut self, item: &ast::Item) {
222-
self.load_links_in_attrs(&item.attrs); // Outer attribute scope
262+
self.resolve_doc_links_local(&item.attrs); // Outer attribute scope
223263
if let ItemKind::Mod(..) = item.kind {
224264
let old_mod = mem::replace(&mut self.current_mod, self.resolver.local_def_id(item.id));
225-
self.load_links_in_attrs(&item.attrs); // Inner attribute scope
265+
self.resolve_doc_links_local(&item.attrs); // Inner attribute scope
226266
self.process_module_children_or_reexports(self.current_mod.to_def_id());
227267
visit::walk_item(self, item);
228268
self.current_mod = old_mod;
@@ -241,22 +281,22 @@ impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
241281
}
242282

243283
fn visit_assoc_item(&mut self, item: &ast::AssocItem, ctxt: AssocCtxt) {
244-
self.load_links_in_attrs(&item.attrs);
284+
self.resolve_doc_links_local(&item.attrs);
245285
visit::walk_assoc_item(self, item, ctxt)
246286
}
247287

248288
fn visit_foreign_item(&mut self, item: &ast::ForeignItem) {
249-
self.load_links_in_attrs(&item.attrs);
289+
self.resolve_doc_links_local(&item.attrs);
250290
visit::walk_foreign_item(self, item)
251291
}
252292

253293
fn visit_variant(&mut self, v: &ast::Variant) {
254-
self.load_links_in_attrs(&v.attrs);
294+
self.resolve_doc_links_local(&v.attrs);
255295
visit::walk_variant(self, v)
256296
}
257297

258298
fn visit_field_def(&mut self, field: &ast::FieldDef) {
259-
self.load_links_in_attrs(&field.attrs);
299+
self.resolve_doc_links_local(&field.attrs);
260300
visit::walk_field_def(self, field)
261301
}
262302

0 commit comments

Comments
 (0)