]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/visit_ast.rs
Rollup merge of #89344 - jackh726:maybe-bound-eror, r=cjgillot
[rust.git] / src / librustdoc / visit_ast.rs
1 //! The Rust AST Visitor. Extracts useful information and massages it into a form
2 //! usable for `clean`.
3
4 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
5 use rustc_hir as hir;
6 use rustc_hir::def::{DefKind, Res};
7 use rustc_hir::def_id::DefId;
8 use rustc_hir::Node;
9 use rustc_middle::middle::privacy::AccessLevel;
10 use rustc_middle::ty::TyCtxt;
11 use rustc_span;
12 use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE};
13 use rustc_span::source_map::Spanned;
14 use rustc_span::symbol::{kw, sym, Symbol};
15
16 use std::mem;
17
18 use crate::clean::{self, AttributesExt, NestedAttributesExt};
19 use crate::core;
20 use crate::doctree::*;
21
22 // FIXME: Should this be replaced with tcx.def_path_str?
23 fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> {
24     let crate_name = tcx.crate_name(did.krate).to_string();
25     let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| {
26         // extern blocks have an empty name
27         let s = elem.data.to_string();
28         if !s.is_empty() { Some(s) } else { None }
29     });
30     std::iter::once(crate_name).chain(relative).collect()
31 }
32
33 crate fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool {
34     while let Some(id) = tcx.hir().get_enclosing_scope(node) {
35         node = id;
36         if tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) {
37             return true;
38         }
39     }
40     false
41 }
42
43 // Also, is there some reason that this doesn't use the 'visit'
44 // framework from syntax?.
45
46 crate struct RustdocVisitor<'a, 'tcx> {
47     cx: &'a mut core::DocContext<'tcx>,
48     view_item_stack: FxHashSet<hir::HirId>,
49     inlining: bool,
50     /// Are the current module and all of its parents public?
51     inside_public_path: bool,
52     exact_paths: FxHashMap<DefId, Vec<String>>,
53 }
54
55 impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
56     crate fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> {
57         // If the root is re-exported, terminate all recursion.
58         let mut stack = FxHashSet::default();
59         stack.insert(hir::CRATE_HIR_ID);
60         RustdocVisitor {
61             cx,
62             view_item_stack: stack,
63             inlining: false,
64             inside_public_path: true,
65             exact_paths: FxHashMap::default(),
66         }
67     }
68
69     fn store_path(&mut self, did: DefId) {
70         let tcx = self.cx.tcx;
71         self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
72     }
73
74     crate fn visit(mut self, krate: &'tcx hir::Crate<'_>) -> Module<'tcx> {
75         let span = krate.module().inner;
76         let mut top_level_module = self.visit_mod_contents(
77             &Spanned { span, node: hir::VisibilityKind::Public },
78             hir::CRATE_HIR_ID,
79             &krate.module(),
80             self.cx.tcx.crate_name(LOCAL_CRATE),
81         );
82
83         // `#[macro_export] macro_rules!` items are reexported at the top level of the
84         // crate, regardless of where they're defined. We want to document the
85         // top level rexport of the macro, not its original definition, since
86         // the rexport defines the path that a user will actually see. Accordingly,
87         // we add the rexport as an item here, and then skip over the original
88         // definition in `visit_item()` below.
89         for export in self.cx.tcx.module_exports(CRATE_DEF_ID).unwrap_or(&[]) {
90             if let Res::Def(DefKind::Macro(_), def_id) = export.res {
91                 if let Some(local_def_id) = def_id.as_local() {
92                     if self.cx.tcx.has_attr(def_id, sym::macro_export) {
93                         let hir_id = self.cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
94                         let item = self.cx.tcx.hir().expect_item(hir_id);
95                         top_level_module.items.push((item, None));
96                     }
97                 }
98             }
99         }
100         self.cx.cache.exact_paths = self.exact_paths;
101         top_level_module
102     }
103
104     fn visit_mod_contents(
105         &mut self,
106         vis: &hir::Visibility<'_>,
107         id: hir::HirId,
108         m: &'tcx hir::Mod<'tcx>,
109         name: Symbol,
110     ) -> Module<'tcx> {
111         let mut om = Module::new(name, id, m.inner);
112         // Keep track of if there were any private modules in the path.
113         let orig_inside_public_path = self.inside_public_path;
114         self.inside_public_path &= vis.node.is_pub();
115         for &i in m.item_ids {
116             let item = self.cx.tcx.hir().item(i);
117             self.visit_item(item, None, &mut om);
118         }
119         self.inside_public_path = orig_inside_public_path;
120         om
121     }
122
123     /// Tries to resolve the target of a `pub use` statement and inlines the
124     /// target if it is defined locally and would not be documented otherwise,
125     /// or when it is specifically requested with `please_inline`.
126     /// (the latter is the case when the import is marked `doc(inline)`)
127     ///
128     /// Cross-crate inlining occurs later on during crate cleaning
129     /// and follows different rules.
130     ///
131     /// Returns `true` if the target has been inlined.
132     fn maybe_inline_local(
133         &mut self,
134         id: hir::HirId,
135         res: Res,
136         renamed: Option<Symbol>,
137         glob: bool,
138         om: &mut Module<'tcx>,
139         please_inline: bool,
140     ) -> bool {
141         debug!("maybe_inline_local res: {:?}", res);
142
143         let tcx = self.cx.tcx;
144         let res_did = if let Some(did) = res.opt_def_id() {
145             did
146         } else {
147             return false;
148         };
149
150         let use_attrs = tcx.hir().attrs(id);
151         // Don't inline `doc(hidden)` imports so they can be stripped at a later stage.
152         let is_no_inline = use_attrs.lists(sym::doc).has_word(sym::no_inline)
153             || use_attrs.lists(sym::doc).has_word(sym::hidden);
154
155         // For cross-crate impl inlining we need to know whether items are
156         // reachable in documentation -- a previously unreachable item can be
157         // made reachable by cross-crate inlining which we're checking here.
158         // (this is done here because we need to know this upfront).
159         if !res_did.is_local() && !is_no_inline {
160             let attrs = clean::inline::load_attrs(self.cx, res_did);
161             let self_is_hidden = attrs.lists(sym::doc).has_word(sym::hidden);
162             if !self_is_hidden {
163                 if let Res::Def(kind, did) = res {
164                     if kind == DefKind::Mod {
165                         crate::visit_lib::LibEmbargoVisitor::new(self.cx).visit_mod(did)
166                     } else {
167                         // All items need to be handled here in case someone wishes to link
168                         // to them with intra-doc links
169                         self.cx.cache.access_levels.map.insert(did, AccessLevel::Public);
170                     }
171                 }
172             }
173             return false;
174         }
175
176         let res_hir_id = match res_did.as_local() {
177             Some(n) => tcx.hir().local_def_id_to_hir_id(n),
178             None => return false,
179         };
180
181         let is_private = !self.cx.cache.access_levels.is_public(res_did);
182         let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id);
183
184         // Only inline if requested or if the item would otherwise be stripped.
185         if (!please_inline && !is_private && !is_hidden) || is_no_inline {
186             return false;
187         }
188
189         if !self.view_item_stack.insert(res_hir_id) {
190             return false;
191         }
192
193         let ret = match tcx.hir().get(res_hir_id) {
194             Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => {
195                 let prev = mem::replace(&mut self.inlining, true);
196                 for &i in m.item_ids {
197                     let i = self.cx.tcx.hir().item(i);
198                     self.visit_item(i, None, om);
199                 }
200                 self.inlining = prev;
201                 true
202             }
203             Node::Item(it) if !glob => {
204                 let prev = mem::replace(&mut self.inlining, true);
205                 self.visit_item(it, renamed, om);
206                 self.inlining = prev;
207                 true
208             }
209             Node::ForeignItem(it) if !glob => {
210                 let prev = mem::replace(&mut self.inlining, true);
211                 self.visit_foreign_item(it, renamed, om);
212                 self.inlining = prev;
213                 true
214             }
215             _ => false,
216         };
217         self.view_item_stack.remove(&res_hir_id);
218         ret
219     }
220
221     fn visit_item(
222         &mut self,
223         item: &'tcx hir::Item<'_>,
224         renamed: Option<Symbol>,
225         om: &mut Module<'tcx>,
226     ) {
227         debug!("visiting item {:?}", item);
228         let name = renamed.unwrap_or(item.ident.name);
229
230         let def_id = item.def_id.to_def_id();
231         let is_pub = item.vis.node.is_pub() || self.cx.tcx.has_attr(def_id, sym::macro_export);
232
233         if is_pub {
234             self.store_path(item.def_id.to_def_id());
235         }
236
237         match item.kind {
238             hir::ItemKind::ForeignMod { items, .. } => {
239                 for item in items {
240                     let item = self.cx.tcx.hir().foreign_item(item.id);
241                     self.visit_foreign_item(item, None, om);
242                 }
243             }
244             // If we're inlining, skip private items.
245             _ if self.inlining && !is_pub => {}
246             hir::ItemKind::GlobalAsm(..) => {}
247             hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
248             hir::ItemKind::Use(ref path, kind) => {
249                 let is_glob = kind == hir::UseKind::Glob;
250
251                 // Struct and variant constructors and proc macro stubs always show up alongside
252                 // their definitions, we've already processed them so just discard these.
253                 if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = path.res {
254                     return;
255                 }
256
257                 let attrs = self.cx.tcx.hir().attrs(item.hir_id());
258
259                 // If there was a private module in the current path then don't bother inlining
260                 // anything as it will probably be stripped anyway.
261                 if is_pub && self.inside_public_path {
262                     let please_inline = attrs.iter().any(|item| match item.meta_item_list() {
263                         Some(ref list) if item.has_name(sym::doc) => {
264                             list.iter().any(|i| i.has_name(sym::inline))
265                         }
266                         _ => false,
267                     });
268                     let ident = if is_glob { None } else { Some(name) };
269                     if self.maybe_inline_local(
270                         item.hir_id(),
271                         path.res,
272                         ident,
273                         is_glob,
274                         om,
275                         please_inline,
276                     ) {
277                         return;
278                     }
279                 }
280
281                 om.items.push((item, renamed))
282             }
283             hir::ItemKind::Macro(ref macro_def) => {
284                 // `#[macro_export] macro_rules!` items are handled seperately in `visit()`,
285                 // above, since they need to be documented at the module top level. Accordingly,
286                 // we only want to handle macros if one of three conditions holds:
287                 //
288                 // 1. This macro was defined by `macro`, and thus isn't covered by the case
289                 //    above.
290                 // 2. This macro isn't marked with `#[macro_export]`, and thus isn't covered
291                 //    by the case above.
292                 // 3. We're inlining, since a reexport where inlining has been requested
293                 //    should be inlined even if it is also documented at the top level.
294
295                 let def_id = item.def_id.to_def_id();
296                 let is_macro_2_0 = !macro_def.macro_rules;
297                 let nonexported = !self.cx.tcx.has_attr(def_id, sym::macro_export);
298
299                 if is_macro_2_0 || nonexported || self.inlining {
300                     om.items.push((item, renamed));
301                 }
302             }
303             hir::ItemKind::Mod(ref m) => {
304                 om.mods.push(self.visit_mod_contents(&item.vis, item.hir_id(), m, name));
305             }
306             hir::ItemKind::Fn(..)
307             | hir::ItemKind::ExternCrate(..)
308             | hir::ItemKind::Enum(..)
309             | hir::ItemKind::Struct(..)
310             | hir::ItemKind::Union(..)
311             | hir::ItemKind::TyAlias(..)
312             | hir::ItemKind::OpaqueTy(..)
313             | hir::ItemKind::Static(..)
314             | hir::ItemKind::Trait(..)
315             | hir::ItemKind::TraitAlias(..) => om.items.push((item, renamed)),
316             hir::ItemKind::Const(..) => {
317                 // Underscore constants do not correspond to a nameable item and
318                 // so are never useful in documentation.
319                 if name != kw::Underscore {
320                     om.items.push((item, renamed));
321                 }
322             }
323             hir::ItemKind::Impl(ref impl_) => {
324                 // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick
325                 // them up regardless of where they're located.
326                 if !self.inlining && impl_.of_trait.is_none() {
327                     om.items.push((item, None));
328                 }
329             }
330         }
331     }
332
333     fn visit_foreign_item(
334         &mut self,
335         item: &'tcx hir::ForeignItem<'_>,
336         renamed: Option<Symbol>,
337         om: &mut Module<'tcx>,
338     ) {
339         // If inlining we only want to include public functions.
340         if !self.inlining || item.vis.node.is_pub() {
341             om.foreigns.push((item, renamed));
342         }
343     }
344 }