]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/collect_intra_doc_links/early.rs
Auto merge of #95107 - r00ster91:fmt, r=joshtriplett
[rust.git] / src / librustdoc / passes / collect_intra_doc_links / early.rs
1 use crate::clean;
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::{Span, SyntaxContext, DUMMY_SP};
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 ) -> ResolverCaches {
26     let mut loader = IntraLinkCrateLoader {
27         resolver,
28         current_mod: CRATE_DEF_ID,
29         visited_mods: Default::default(),
30         traits_in_scope: Default::default(),
31         all_traits: Default::default(),
32         all_trait_impls: Default::default(),
33     };
34
35     // Because of the `crate::` prefix, any doc comment can reference
36     // the crate root's set of in-scope traits. This line makes sure
37     // it's available.
38     loader.add_traits_in_scope(CRATE_DEF_ID.to_def_id());
39
40     // Overridden `visit_item` below doesn't apply to the crate root,
41     // so we have to visit its attributes and reexports separately.
42     loader.load_links_in_attrs(&krate.attrs, krate.spans.inner_span);
43     loader.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id());
44     visit::walk_crate(&mut loader, krate);
45     loader.add_foreign_traits_in_scope();
46
47     // FIXME: somehow rustdoc is still missing crates even though we loaded all
48     // the known necessary crates. Load them all unconditionally until we find a way to fix this.
49     // DO NOT REMOVE THIS without first testing on the reproducer in
50     // https://github.com/jyn514/objr/commit/edcee7b8124abf0e4c63873e8422ff81beb11ebb
51     for (extern_name, _) in externs.iter().filter(|(_, entry)| entry.add_prelude) {
52         let _ = loader.resolver.resolve_str_path_error(
53             DUMMY_SP,
54             extern_name,
55             TypeNS,
56             CRATE_DEF_ID.to_def_id(),
57         );
58     }
59
60     ResolverCaches {
61         traits_in_scope: loader.traits_in_scope,
62         all_traits: Some(loader.all_traits),
63         all_trait_impls: Some(loader.all_trait_impls),
64     }
65 }
66
67 struct IntraLinkCrateLoader<'r, 'ra> {
68     resolver: &'r mut Resolver<'ra>,
69     current_mod: LocalDefId,
70     visited_mods: DefIdSet,
71     traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
72     all_traits: Vec<DefId>,
73     all_trait_impls: Vec<DefId>,
74 }
75
76 impl IntraLinkCrateLoader<'_, '_> {
77     fn add_traits_in_scope(&mut self, def_id: DefId) {
78         // Calls to `traits_in_scope` are expensive, so try to avoid them if only possible.
79         // Keys in the `traits_in_scope` cache are always module IDs.
80         if let Entry::Vacant(entry) = self.traits_in_scope.entry(def_id) {
81             let module = self.resolver.get_nearest_non_block_module(def_id);
82             let module_id = module.def_id();
83             let entry = if module_id == def_id {
84                 Some(entry)
85             } else if let Entry::Vacant(entry) = self.traits_in_scope.entry(module_id) {
86                 Some(entry)
87             } else {
88                 None
89             };
90             if let Some(entry) = entry {
91                 entry.insert(self.resolver.traits_in_scope(
92                     None,
93                     &ParentScope::module(module, self.resolver),
94                     SyntaxContext::root(),
95                     None,
96                 ));
97             }
98         }
99     }
100
101     fn add_traits_in_parent_scope(&mut self, def_id: DefId) {
102         if let Some(module_id) = self.resolver.parent(def_id) {
103             self.add_traits_in_scope(module_id);
104         }
105     }
106
107     /// Add traits in scope for links in impls collected by the `collect-intra-doc-links` pass.
108     /// That pass filters impls using type-based information, but we don't yet have such
109     /// information here, so we just conservatively calculate traits in scope for *all* modules
110     /// having impls in them.
111     fn add_foreign_traits_in_scope(&mut self) {
112         for cnum in Vec::from_iter(self.resolver.cstore().crates_untracked()) {
113             // FIXME: Due to #78696 rustdoc can query traits in scope for any crate root.
114             self.add_traits_in_scope(cnum.as_def_id());
115
116             let all_traits = Vec::from_iter(self.resolver.cstore().traits_in_crate_untracked(cnum));
117             let all_trait_impls =
118                 Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
119             let all_inherent_impls =
120                 Vec::from_iter(self.resolver.cstore().inherent_impls_in_crate_untracked(cnum));
121             let all_lang_items = Vec::from_iter(self.resolver.cstore().lang_items_untracked(cnum));
122
123             // Querying traits in scope is expensive so we try to prune the impl and traits lists
124             // using privacy, private traits and impls from other crates are never documented in
125             // the current crate, and links in their doc comments are not resolved.
126             for &def_id in &all_traits {
127                 if self.resolver.cstore().visibility_untracked(def_id) == Visibility::Public {
128                     self.add_traits_in_parent_scope(def_id);
129                 }
130             }
131             for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
132                 if self.resolver.cstore().visibility_untracked(trait_def_id) == 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) == Visibility::Public
135                     })
136                 {
137                     self.add_traits_in_parent_scope(impl_def_id);
138                 }
139             }
140             for (ty_def_id, impl_def_id) in all_inherent_impls {
141                 if self.resolver.cstore().visibility_untracked(ty_def_id) == Visibility::Public {
142                     self.add_traits_in_parent_scope(impl_def_id);
143                 }
144             }
145             for def_id in all_lang_items {
146                 self.add_traits_in_parent_scope(def_id);
147             }
148
149             self.all_traits.extend(all_traits);
150             self.all_trait_impls.extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id));
151         }
152     }
153
154     fn load_links_in_attrs(&mut self, attrs: &[ast::Attribute], span: Span) {
155         // FIXME: this needs to consider reexport inlining.
156         let attrs = clean::Attributes::from_ast(attrs, None);
157         for (parent_module, doc) in attrs.collapsed_doc_value_by_module_level() {
158             let module_id = parent_module.unwrap_or(self.current_mod.to_def_id());
159
160             self.add_traits_in_scope(module_id);
161
162             for link in markdown_links(&doc.as_str()) {
163                 let path_str = if let Some(Ok(x)) = preprocess_link(&link) {
164                     x.path_str
165                 } else {
166                     continue;
167                 };
168                 let _ = self.resolver.resolve_str_path_error(span, &path_str, TypeNS, module_id);
169             }
170         }
171     }
172
173     /// When reexports are inlined, they are replaced with item which they refer to, those items
174     /// may have links in their doc comments, those links are resolved at the item definition site,
175     /// so we need to know traits in scope at that definition site.
176     fn process_module_children_or_reexports(&mut self, module_id: DefId) {
177         if !self.visited_mods.insert(module_id) {
178             return; // avoid infinite recursion
179         }
180
181         for child in self.resolver.module_children_or_reexports(module_id) {
182             if child.vis == Visibility::Public {
183                 if let Some(def_id) = child.res.opt_def_id() {
184                     self.add_traits_in_parent_scope(def_id);
185                 }
186                 if let Res::Def(DefKind::Mod, module_id) = child.res {
187                     self.process_module_children_or_reexports(module_id);
188                 }
189             }
190         }
191     }
192 }
193
194 impl Visitor<'_> for IntraLinkCrateLoader<'_, '_> {
195     fn visit_item(&mut self, item: &ast::Item) {
196         if let ItemKind::Mod(..) = item.kind {
197             let old_mod = mem::replace(&mut self.current_mod, self.resolver.local_def_id(item.id));
198
199             // A module written with a outline doc comments will resolve traits relative
200             // to the parent module. Make sure the parent module's traits-in-scope are
201             // loaded, even if the module itself has no doc comments.
202             self.add_traits_in_parent_scope(self.current_mod.to_def_id());
203
204             self.load_links_in_attrs(&item.attrs, item.span);
205             self.process_module_children_or_reexports(self.current_mod.to_def_id());
206             visit::walk_item(self, item);
207
208             self.current_mod = old_mod;
209         } else {
210             match item.kind {
211                 ItemKind::Trait(..) => {
212                     self.all_traits.push(self.resolver.local_def_id(item.id).to_def_id());
213                 }
214                 ItemKind::Impl(box ast::Impl { of_trait: Some(..), .. }) => {
215                     self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id());
216                 }
217                 _ => {}
218             }
219             self.load_links_in_attrs(&item.attrs, item.span);
220             visit::walk_item(self, item);
221         }
222     }
223
224     fn visit_assoc_item(&mut self, item: &ast::AssocItem, ctxt: AssocCtxt) {
225         self.load_links_in_attrs(&item.attrs, item.span);
226         visit::walk_assoc_item(self, item, ctxt)
227     }
228
229     fn visit_foreign_item(&mut self, item: &ast::ForeignItem) {
230         self.load_links_in_attrs(&item.attrs, item.span);
231         visit::walk_foreign_item(self, item)
232     }
233
234     fn visit_variant(&mut self, v: &ast::Variant) {
235         self.load_links_in_attrs(&v.attrs, v.span);
236         visit::walk_variant(self, v)
237     }
238
239     fn visit_field_def(&mut self, field: &ast::FieldDef) {
240         self.load_links_in_attrs(&field.attrs, field.span);
241         visit::walk_field_def(self, field)
242     }
243
244     // NOTE: if doc-comments are ever allowed on other nodes (e.g. function parameters),
245     // then this will have to implement other visitor methods too.
246 }