]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/collect_intra_doc_links/early.rs
rustdoc: remove unused CSS `#main-content > table td`
[rust.git] / src / librustdoc / passes / collect_intra_doc_links / early.rs
1 use crate::clean::Attributes;
2 use crate::core::ResolverCaches;
3 use crate::passes::collect_intra_doc_links::preprocessed_markdown_links;
4 use crate::passes::collect_intra_doc_links::{Disambiguator, PreprocessedMarkdownLink};
5
6 use rustc_ast::visit::{self, AssocCtxt, Visitor};
7 use rustc_ast::{self as ast, ItemKind};
8 use rustc_data_structures::fx::FxHashMap;
9 use rustc_hir::def::Namespace::*;
10 use rustc_hir::def::{DefKind, Namespace, Res};
11 use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, CRATE_DEF_ID};
12 use rustc_hir::TraitCandidate;
13 use rustc_middle::ty::{DefIdTree, Visibility};
14 use rustc_resolve::{ParentScope, Resolver};
15 use rustc_session::config::Externs;
16 use rustc_session::Session;
17 use rustc_span::symbol::sym;
18 use rustc_span::{Symbol, SyntaxContext};
19
20 use std::collections::hash_map::Entry;
21 use std::mem;
22
23 pub(crate) fn early_resolve_intra_doc_links(
24     resolver: &mut Resolver<'_>,
25     sess: &Session,
26     krate: &ast::Crate,
27     externs: Externs,
28     document_private_items: bool,
29 ) -> ResolverCaches {
30     let parent_scope =
31         ParentScope::module(resolver.expect_module(CRATE_DEF_ID.to_def_id()), resolver);
32     let mut link_resolver = EarlyDocLinkResolver {
33         resolver,
34         sess,
35         parent_scope,
36         visited_mods: Default::default(),
37         markdown_links: Default::default(),
38         doc_link_resolutions: Default::default(),
39         traits_in_scope: Default::default(),
40         all_traits: Default::default(),
41         all_trait_impls: Default::default(),
42         all_macro_rules: Default::default(),
43         document_private_items,
44     };
45
46     // Overridden `visit_item` below doesn't apply to the crate root,
47     // so we have to visit its attributes and reexports separately.
48     link_resolver.resolve_doc_links_local(&krate.attrs);
49     link_resolver.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id());
50     visit::walk_crate(&mut link_resolver, krate);
51     link_resolver.process_extern_impls();
52
53     // FIXME: somehow rustdoc is still missing crates even though we loaded all
54     // the known necessary crates. Load them all unconditionally until we find a way to fix this.
55     // DO NOT REMOVE THIS without first testing on the reproducer in
56     // https://github.com/jyn514/objr/commit/edcee7b8124abf0e4c63873e8422ff81beb11ebb
57     for (extern_name, _) in externs.iter().filter(|(_, entry)| entry.add_prelude) {
58         link_resolver.resolver.resolve_rustdoc_path(extern_name, TypeNS, parent_scope);
59     }
60
61     ResolverCaches {
62         markdown_links: Some(link_resolver.markdown_links),
63         doc_link_resolutions: link_resolver.doc_link_resolutions,
64         traits_in_scope: link_resolver.traits_in_scope,
65         all_traits: Some(link_resolver.all_traits),
66         all_trait_impls: Some(link_resolver.all_trait_impls),
67         all_macro_rules: link_resolver.all_macro_rules,
68     }
69 }
70
71 fn doc_attrs<'a>(attrs: impl Iterator<Item = &'a ast::Attribute>) -> Attributes {
72     Attributes::from_ast_iter(attrs.map(|attr| (attr, None)), true)
73 }
74
75 struct EarlyDocLinkResolver<'r, 'ra> {
76     resolver: &'r mut Resolver<'ra>,
77     sess: &'r Session,
78     parent_scope: ParentScope<'ra>,
79     visited_mods: DefIdSet,
80     markdown_links: FxHashMap<String, Vec<PreprocessedMarkdownLink>>,
81     doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<ast::NodeId>>>,
82     traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
83     all_traits: Vec<DefId>,
84     all_trait_impls: Vec<DefId>,
85     all_macro_rules: FxHashMap<Symbol, Res<ast::NodeId>>,
86     document_private_items: bool,
87 }
88
89 impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
90     fn add_traits_in_scope(&mut self, def_id: DefId) {
91         // Calls to `traits_in_scope` are expensive, so try to avoid them if only possible.
92         // Keys in the `traits_in_scope` cache are always module IDs.
93         if let Entry::Vacant(entry) = self.traits_in_scope.entry(def_id) {
94             let module = self.resolver.get_nearest_non_block_module(def_id);
95             let module_id = module.def_id();
96             let entry = if module_id == def_id {
97                 Some(entry)
98             } else if let Entry::Vacant(entry) = self.traits_in_scope.entry(module_id) {
99                 Some(entry)
100             } else {
101                 None
102             };
103             if let Some(entry) = entry {
104                 entry.insert(self.resolver.traits_in_scope(
105                     None,
106                     &ParentScope::module(module, self.resolver),
107                     SyntaxContext::root(),
108                     None,
109                 ));
110             }
111         }
112     }
113
114     /// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass.
115     /// That pass filters impls using type-based information, but we don't yet have such
116     /// information here, so we just conservatively calculate traits in scope for *all* modules
117     /// having impls in them.
118     fn process_extern_impls(&mut self) {
119         // Resolving links in already existing crates may trigger loading of new crates.
120         let mut start_cnum = 0;
121         loop {
122             let crates = Vec::from_iter(self.resolver.cstore().crates_untracked());
123             for &cnum in &crates[start_cnum..] {
124                 let all_traits =
125                     Vec::from_iter(self.resolver.cstore().traits_in_crate_untracked(cnum));
126                 let all_trait_impls =
127                     Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
128                 let all_inherent_impls =
129                     Vec::from_iter(self.resolver.cstore().inherent_impls_in_crate_untracked(cnum));
130                 let all_incoherent_impls = Vec::from_iter(
131                     self.resolver.cstore().incoherent_impls_in_crate_untracked(cnum),
132                 );
133
134                 // Querying traits in scope is expensive so we try to prune the impl and traits lists
135                 // using privacy, private traits and impls from other crates are never documented in
136                 // the current crate, and links in their doc comments are not resolved.
137                 for &def_id in &all_traits {
138                     if self.resolver.cstore().visibility_untracked(def_id).is_public() {
139                         self.resolve_doc_links_extern_impl(def_id, false);
140                     }
141                 }
142                 for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
143                     if self.resolver.cstore().visibility_untracked(trait_def_id).is_public()
144                         && simplified_self_ty.and_then(|ty| ty.def()).map_or(true, |ty_def_id| {
145                             self.resolver.cstore().visibility_untracked(ty_def_id).is_public()
146                         })
147                     {
148                         self.resolve_doc_links_extern_impl(impl_def_id, false);
149                     }
150                 }
151                 for (ty_def_id, impl_def_id) in all_inherent_impls {
152                     if self.resolver.cstore().visibility_untracked(ty_def_id).is_public() {
153                         self.resolve_doc_links_extern_impl(impl_def_id, true);
154                     }
155                 }
156                 for impl_def_id in all_incoherent_impls {
157                     self.resolve_doc_links_extern_impl(impl_def_id, true);
158                 }
159
160                 self.all_traits.extend(all_traits);
161                 self.all_trait_impls
162                     .extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id));
163             }
164
165             if crates.len() > start_cnum {
166                 start_cnum = crates.len();
167             } else {
168                 break;
169             }
170         }
171     }
172
173     fn resolve_doc_links_extern_impl(&mut self, def_id: DefId, is_inherent: bool) {
174         self.resolve_doc_links_extern_outer_fixme(def_id, def_id);
175         let assoc_item_def_ids = Vec::from_iter(
176             self.resolver.cstore().associated_item_def_ids_untracked(def_id, self.sess),
177         );
178         for assoc_def_id in assoc_item_def_ids {
179             if !is_inherent || self.resolver.cstore().visibility_untracked(assoc_def_id).is_public()
180             {
181                 self.resolve_doc_links_extern_outer_fixme(assoc_def_id, def_id);
182             }
183         }
184     }
185
186     // FIXME: replace all uses with `resolve_doc_links_extern_outer` to actually resolve links, not
187     // just add traits in scope. This may be expensive and require benchmarking and optimization.
188     fn resolve_doc_links_extern_outer_fixme(&mut self, def_id: DefId, scope_id: DefId) {
189         if !self.resolver.cstore().may_have_doc_links_untracked(def_id) {
190             return;
191         }
192         if let Some(parent_id) = self.resolver.opt_parent(scope_id) {
193             self.add_traits_in_scope(parent_id);
194         }
195     }
196
197     fn resolve_doc_links_extern_outer(&mut self, def_id: DefId, scope_id: DefId) {
198         if !self.resolver.cstore().may_have_doc_links_untracked(def_id) {
199             return;
200         }
201         let attrs = Vec::from_iter(self.resolver.cstore().item_attrs_untracked(def_id, self.sess));
202         let parent_scope = ParentScope::module(
203             self.resolver.get_nearest_non_block_module(
204                 self.resolver.opt_parent(scope_id).unwrap_or(scope_id),
205             ),
206             self.resolver,
207         );
208         self.resolve_doc_links(doc_attrs(attrs.iter()), parent_scope);
209     }
210
211     fn resolve_doc_links_extern_inner(&mut self, def_id: DefId) {
212         if !self.resolver.cstore().may_have_doc_links_untracked(def_id) {
213             return;
214         }
215         let attrs = Vec::from_iter(self.resolver.cstore().item_attrs_untracked(def_id, self.sess));
216         let parent_scope = ParentScope::module(self.resolver.expect_module(def_id), self.resolver);
217         self.resolve_doc_links(doc_attrs(attrs.iter()), parent_scope);
218     }
219
220     fn resolve_doc_links_local(&mut self, attrs: &[ast::Attribute]) {
221         if !attrs.iter().any(|attr| attr.may_have_doc_links()) {
222             return;
223         }
224         self.resolve_doc_links(doc_attrs(attrs.iter()), self.parent_scope);
225     }
226
227     fn resolve_and_cache(
228         &mut self,
229         path_str: &str,
230         ns: Namespace,
231         parent_scope: &ParentScope<'ra>,
232     ) -> bool {
233         // FIXME: This caching may be incorrect in case of multiple `macro_rules`
234         // items with the same name in the same module.
235         self.doc_link_resolutions
236             .entry((Symbol::intern(path_str), ns, parent_scope.module.def_id()))
237             .or_insert_with_key(|(path, ns, _)| {
238                 self.resolver.resolve_rustdoc_path(path.as_str(), *ns, *parent_scope)
239             })
240             .is_some()
241     }
242
243     fn resolve_doc_links(&mut self, attrs: Attributes, parent_scope: ParentScope<'ra>) {
244         let mut need_traits_in_scope = false;
245         for (doc_module, doc) in attrs.prepare_to_doc_link_resolution() {
246             assert_eq!(doc_module, None);
247             let mut tmp_links = mem::take(&mut self.markdown_links);
248             let links =
249                 tmp_links.entry(doc).or_insert_with_key(|doc| preprocessed_markdown_links(doc));
250             for PreprocessedMarkdownLink(pp_link, _) in links {
251                 if let Ok(pinfo) = pp_link {
252                     // The logic here is a conservative approximation for path resolution in
253                     // `resolve_with_disambiguator`.
254                     if let Some(ns) = pinfo.disambiguator.map(Disambiguator::ns) {
255                         if self.resolve_and_cache(&pinfo.path_str, ns, &parent_scope) {
256                             continue;
257                         }
258                     }
259
260                     // Resolve all namespaces due to no disambiguator or for diagnostics.
261                     let mut any_resolved = false;
262                     let mut need_assoc = false;
263                     for ns in [TypeNS, ValueNS, MacroNS] {
264                         if self.resolve_and_cache(&pinfo.path_str, ns, &parent_scope) {
265                             any_resolved = true;
266                         } else if ns != MacroNS {
267                             need_assoc = true;
268                         }
269                     }
270
271                     // Resolve all prefixes for type-relative resolution or for diagnostics.
272                     if need_assoc || !any_resolved {
273                         let mut path = &pinfo.path_str[..];
274                         while let Some(idx) = path.rfind("::") {
275                             path = &path[..idx];
276                             need_traits_in_scope = true;
277                             for ns in [TypeNS, ValueNS, MacroNS] {
278                                 self.resolve_and_cache(path, ns, &parent_scope);
279                             }
280                         }
281                     }
282                 }
283             }
284             self.markdown_links = tmp_links;
285         }
286
287         if need_traits_in_scope {
288             self.add_traits_in_scope(parent_scope.module.def_id());
289         }
290     }
291
292     /// When reexports are inlined, they are replaced with item which they refer to, those items
293     /// may have links in their doc comments, those links are resolved at the item definition site,
294     /// so we need to know traits in scope at that definition site.
295     fn process_module_children_or_reexports(&mut self, module_id: DefId) {
296         if !self.visited_mods.insert(module_id) {
297             return; // avoid infinite recursion
298         }
299
300         for child in self.resolver.module_children_or_reexports(module_id) {
301             // This condition should give a superset of `denied` from `fn clean_use_statement`.
302             if child.vis.is_public()
303                 || self.document_private_items
304                     && child.vis != Visibility::Restricted(module_id)
305                     && module_id.is_local()
306             {
307                 if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() {
308                     let scope_id = match child.res {
309                         Res::Def(DefKind::Variant, ..) => self.resolver.parent(def_id),
310                         _ => def_id,
311                     };
312                     self.resolve_doc_links_extern_outer(def_id, scope_id); // Outer attribute scope
313                     if let Res::Def(DefKind::Mod, ..) = child.res {
314                         self.resolve_doc_links_extern_inner(def_id); // Inner attribute scope
315                     }
316                     // `DefKind::Trait`s are processed in `process_extern_impls`.
317                     if let Res::Def(DefKind::Mod | DefKind::Enum, ..) = child.res {
318                         self.process_module_children_or_reexports(def_id);
319                     }
320                     if let Res::Def(DefKind::Struct | DefKind::Union | DefKind::Variant, _) =
321                         child.res
322                     {
323                         let field_def_ids = Vec::from_iter(
324                             self.resolver
325                                 .cstore()
326                                 .associated_item_def_ids_untracked(def_id, self.sess),
327                         );
328                         for field_def_id in field_def_ids {
329                             self.resolve_doc_links_extern_outer(field_def_id, scope_id);
330                         }
331                     }
332                 }
333             }
334         }
335     }
336 }
337
338 impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
339     fn visit_item(&mut self, item: &ast::Item) {
340         self.resolve_doc_links_local(&item.attrs); // Outer attribute scope
341         if let ItemKind::Mod(..) = item.kind {
342             let module_def_id = self.resolver.local_def_id(item.id).to_def_id();
343             let module = self.resolver.expect_module(module_def_id);
344             let old_module = mem::replace(&mut self.parent_scope.module, module);
345             let old_macro_rules = self.parent_scope.macro_rules;
346             self.resolve_doc_links_local(&item.attrs); // Inner attribute scope
347             self.process_module_children_or_reexports(module_def_id);
348             visit::walk_item(self, item);
349             if item
350                 .attrs
351                 .iter()
352                 .all(|attr| !attr.has_name(sym::macro_use) && !attr.has_name(sym::macro_escape))
353             {
354                 self.parent_scope.macro_rules = old_macro_rules;
355             }
356             self.parent_scope.module = old_module;
357         } else {
358             match &item.kind {
359                 ItemKind::Trait(..) => {
360                     self.all_traits.push(self.resolver.local_def_id(item.id).to_def_id());
361                 }
362                 ItemKind::Impl(box ast::Impl { of_trait: Some(..), .. }) => {
363                     self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id());
364                 }
365                 ItemKind::MacroDef(macro_def) if macro_def.macro_rules => {
366                     let (macro_rules_scope, res) =
367                         self.resolver.macro_rules_scope(self.resolver.local_def_id(item.id));
368                     self.parent_scope.macro_rules = macro_rules_scope;
369                     self.all_macro_rules.insert(item.ident.name, res);
370                 }
371                 _ => {}
372             }
373             visit::walk_item(self, item);
374         }
375     }
376
377     fn visit_assoc_item(&mut self, item: &ast::AssocItem, ctxt: AssocCtxt) {
378         self.resolve_doc_links_local(&item.attrs);
379         visit::walk_assoc_item(self, item, ctxt)
380     }
381
382     fn visit_foreign_item(&mut self, item: &ast::ForeignItem) {
383         self.resolve_doc_links_local(&item.attrs);
384         visit::walk_foreign_item(self, item)
385     }
386
387     fn visit_variant(&mut self, v: &ast::Variant) {
388         self.resolve_doc_links_local(&v.attrs);
389         visit::walk_variant(self, v)
390     }
391
392     fn visit_field_def(&mut self, field: &ast::FieldDef) {
393         self.resolve_doc_links_local(&field.attrs);
394         visit::walk_field_def(self, field)
395     }
396
397     fn visit_block(&mut self, block: &ast::Block) {
398         let old_macro_rules = self.parent_scope.macro_rules;
399         visit::walk_block(self, block);
400         self.parent_scope.macro_rules = old_macro_rules;
401     }
402
403     // NOTE: if doc-comments are ever allowed on other nodes (e.g. function parameters),
404     // then this will have to implement other visitor methods too.
405 }