]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/collect_intra_doc_links/early.rs
dffceff045d0b6d6e2cc239c422f6e97ff6f8655
[rust.git] / src / librustdoc / passes / collect_intra_doc_links / early.rs
1 use crate::clean::Attributes;
2 use crate::core::ResolverCaches;
3 use crate::html::markdown::markdown_links;
4 use crate::passes::collect_intra_doc_links::preprocess_link;
5
6 use rustc_ast::visit::{self, AssocCtxt, Visitor};
7 use rustc_ast::{self as ast, ItemKind};
8 use rustc_ast_lowering::ResolverAstLowering;
9 use rustc_hir::def::Namespace::TypeNS;
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};
15 use rustc_session::config::Externs;
16 use rustc_span::SyntaxContext;
17
18 use std::collections::hash_map::Entry;
19 use std::mem;
20
21 crate fn early_resolve_intra_doc_links(
22     resolver: &mut Resolver<'_>,
23     krate: &ast::Crate,
24     externs: Externs,
25     document_private_items: bool,
26 ) -> ResolverCaches {
27     let mut link_resolver = EarlyDocLinkResolver {
28         resolver,
29         current_mod: CRATE_DEF_ID,
30         visited_mods: Default::default(),
31         traits_in_scope: Default::default(),
32         all_traits: Default::default(),
33         all_trait_impls: Default::default(),
34         document_private_items,
35     };
36
37     // Overridden `visit_item` below doesn't apply to the crate root,
38     // so we have to visit its attributes and reexports separately.
39     link_resolver.load_links_in_attrs(&krate.attrs);
40     link_resolver.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id());
41     visit::walk_crate(&mut link_resolver, krate);
42     link_resolver.process_extern_impls();
43
44     // FIXME: somehow rustdoc is still missing crates even though we loaded all
45     // the known necessary crates. Load them all unconditionally until we find a way to fix this.
46     // DO NOT REMOVE THIS without first testing on the reproducer in
47     // https://github.com/jyn514/objr/commit/edcee7b8124abf0e4c63873e8422ff81beb11ebb
48     for (extern_name, _) in externs.iter().filter(|(_, entry)| entry.add_prelude) {
49         link_resolver.resolver.resolve_rustdoc_path(extern_name, TypeNS, CRATE_DEF_ID.to_def_id());
50     }
51
52     ResolverCaches {
53         traits_in_scope: link_resolver.traits_in_scope,
54         all_traits: Some(link_resolver.all_traits),
55         all_trait_impls: Some(link_resolver.all_trait_impls),
56         all_macro_rules: link_resolver.resolver.take_all_macro_rules(),
57     }
58 }
59
60 struct EarlyDocLinkResolver<'r, 'ra> {
61     resolver: &'r mut Resolver<'ra>,
62     current_mod: LocalDefId,
63     visited_mods: DefIdSet,
64     traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
65     all_traits: Vec<DefId>,
66     all_trait_impls: Vec<DefId>,
67     document_private_items: bool,
68 }
69
70 impl EarlyDocLinkResolver<'_, '_> {
71     fn add_traits_in_scope(&mut self, def_id: DefId) {
72         // Calls to `traits_in_scope` are expensive, so try to avoid them if only possible.
73         // Keys in the `traits_in_scope` cache are always module IDs.
74         if let Entry::Vacant(entry) = self.traits_in_scope.entry(def_id) {
75             let module = self.resolver.get_nearest_non_block_module(def_id);
76             let module_id = module.def_id();
77             let entry = if module_id == def_id {
78                 Some(entry)
79             } else if let Entry::Vacant(entry) = self.traits_in_scope.entry(module_id) {
80                 Some(entry)
81             } else {
82                 None
83             };
84             if let Some(entry) = entry {
85                 entry.insert(self.resolver.traits_in_scope(
86                     None,
87                     &ParentScope::module(module, self.resolver),
88                     SyntaxContext::root(),
89                     None,
90                 ));
91             }
92         }
93     }
94
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
101     /// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass.
102     /// That pass filters impls using type-based information, but we don't yet have such
103     /// information here, so we just conservatively calculate traits in scope for *all* modules
104     /// having impls in them.
105     fn process_extern_impls(&mut self) {
106         // FIXME: Need to resolve doc links on all these impl and trait items below.
107         // Resolving links in already existing crates may trigger loading of new crates.
108         let mut start_cnum = 0;
109         loop {
110             let crates = Vec::from_iter(self.resolver.cstore().crates_untracked());
111             for &cnum in &crates[start_cnum..] {
112                 let all_traits =
113                     Vec::from_iter(self.resolver.cstore().traits_in_crate_untracked(cnum));
114                 let all_trait_impls =
115                     Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
116                 let all_inherent_impls =
117                     Vec::from_iter(self.resolver.cstore().inherent_impls_in_crate_untracked(cnum));
118                 let all_incoherent_impls = Vec::from_iter(
119                     self.resolver.cstore().incoherent_impls_in_crate_untracked(cnum),
120                 );
121
122                 // Querying traits in scope is expensive so we try to prune the impl and traits lists
123                 // using privacy, private traits and impls from other crates are never documented in
124                 // the current crate, and links in their doc comments are not resolved.
125                 for &def_id in &all_traits {
126                     if self.resolver.cstore().visibility_untracked(def_id) == Visibility::Public {
127                         self.add_traits_in_parent_scope(def_id);
128                     }
129                 }
130                 for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
131                     if self.resolver.cstore().visibility_untracked(trait_def_id)
132                         == Visibility::Public
133                         && simplified_self_ty.and_then(|ty| ty.def()).map_or(true, |ty_def_id| {
134                             self.resolver.cstore().visibility_untracked(ty_def_id)
135                                 == Visibility::Public
136                         })
137                     {
138                         self.add_traits_in_parent_scope(impl_def_id);
139                     }
140                 }
141                 for (ty_def_id, impl_def_id) in all_inherent_impls {
142                     if self.resolver.cstore().visibility_untracked(ty_def_id) == Visibility::Public
143                     {
144                         self.add_traits_in_parent_scope(impl_def_id);
145                     }
146                 }
147                 for def_id in all_incoherent_impls {
148                     self.add_traits_in_parent_scope(def_id);
149                 }
150
151                 self.all_traits.extend(all_traits);
152                 self.all_trait_impls
153                     .extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id));
154             }
155
156             if crates.len() > start_cnum {
157                 start_cnum = crates.len();
158             } else {
159                 break;
160             }
161         }
162     }
163
164     fn load_links_in_attrs(&mut self, attrs: &[ast::Attribute]) {
165         let module_id = self.current_mod.to_def_id();
166         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         {
170             assert_eq!(doc_module, None);
171             for link in markdown_links(&doc.as_str()) {
172                 if let Some(Ok(pinfo)) = preprocess_link(&link) {
173                     self.resolver.resolve_rustdoc_path(&pinfo.path_str, TypeNS, module_id);
174                     need_traits_in_scope = true;
175                 }
176             }
177         }
178
179         if need_traits_in_scope {
180             self.add_traits_in_scope(module_id);
181         }
182     }
183
184     /// When reexports are inlined, they are replaced with item which they refer to, those items
185     /// may have links in their doc comments, those links are resolved at the item definition site,
186     /// so we need to know traits in scope at that definition site.
187     fn process_module_children_or_reexports(&mut self, module_id: DefId) {
188         if !self.visited_mods.insert(module_id) {
189             return; // avoid infinite recursion
190         }
191
192         for child in self.resolver.module_children_or_reexports(module_id) {
193             // This condition should give a superset of `denied` from `fn clean_use_statement`.
194             if child.vis == Visibility::Public
195                 || self.document_private_items
196                     && child.vis != Visibility::Restricted(module_id)
197                     && module_id.is_local()
198             {
199                 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.
202                     let scope_id = match child.res {
203                         Res::Def(DefKind::Variant, ..) => self.resolver.parent(def_id).unwrap(),
204                         _ => def_id,
205                     };
206                     self.add_traits_in_parent_scope(scope_id); // Outer attribute scope
207                     if let Res::Def(DefKind::Mod, ..) = child.res {
208                         self.add_traits_in_scope(def_id); // Inner attribute scope
209                     }
210                     // Traits are processed in `add_extern_traits_in_scope`.
211                     if let Res::Def(DefKind::Mod | DefKind::Enum, ..) = child.res {
212                         self.process_module_children_or_reexports(def_id);
213                     }
214                 }
215             }
216         }
217     }
218 }
219
220 impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
221     fn visit_item(&mut self, item: &ast::Item) {
222         self.load_links_in_attrs(&item.attrs); // Outer attribute scope
223         if let ItemKind::Mod(..) = item.kind {
224             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
226             self.process_module_children_or_reexports(self.current_mod.to_def_id());
227             visit::walk_item(self, item);
228             self.current_mod = old_mod;
229         } else {
230             match item.kind {
231                 ItemKind::Trait(..) => {
232                     self.all_traits.push(self.resolver.local_def_id(item.id).to_def_id());
233                 }
234                 ItemKind::Impl(box ast::Impl { of_trait: Some(..), .. }) => {
235                     self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id());
236                 }
237                 _ => {}
238             }
239             visit::walk_item(self, item);
240         }
241     }
242
243     fn visit_assoc_item(&mut self, item: &ast::AssocItem, ctxt: AssocCtxt) {
244         self.load_links_in_attrs(&item.attrs);
245         visit::walk_assoc_item(self, item, ctxt)
246     }
247
248     fn visit_foreign_item(&mut self, item: &ast::ForeignItem) {
249         self.load_links_in_attrs(&item.attrs);
250         visit::walk_foreign_item(self, item)
251     }
252
253     fn visit_variant(&mut self, v: &ast::Variant) {
254         self.load_links_in_attrs(&v.attrs);
255         visit::walk_variant(self, v)
256     }
257
258     fn visit_field_def(&mut self, field: &ast::FieldDef) {
259         self.load_links_in_attrs(&field.attrs);
260         visit::walk_field_def(self, field)
261     }
262
263     // NOTE: if doc-comments are ever allowed on other nodes (e.g. function parameters),
264     // then this will have to implement other visitor methods too.
265 }