]>
Commit | Line | Data |
---|---|---|
0731742a XL |
1 | //! The Rust AST Visitor. Extracts useful information and massages it into a form |
2 | //! usable for `clean`. | |
1a4d82fc | 3 | |
dfeec247 XL |
4 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
5 | use rustc_hir as hir; | |
6 | use rustc_hir::def::{DefKind, Res}; | |
fc512014 | 7 | use rustc_hir::def_id::DefId; |
dfeec247 | 8 | use rustc_hir::Node; |
c295e0f8 | 9 | use rustc_hir::CRATE_HIR_ID; |
2b03887a FG |
10 | use rustc_middle::middle::privacy::Level; |
11 | use rustc_middle::ty::{TyCtxt, Visibility}; | |
94222f64 | 12 | use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; |
fc512014 | 13 | use rustc_span::symbol::{kw, sym, Symbol}; |
3c0e092e | 14 | use rustc_span::Span; |
1a4d82fc | 15 | |
0731742a | 16 | use std::mem; |
e9174d1e | 17 | |
c295e0f8 | 18 | use crate::clean::{self, cfg::Cfg, AttributesExt, NestedAttributesExt}; |
dfeec247 | 19 | use crate::core; |
3c0e092e XL |
20 | |
21 | /// This module is used to store stuff from Rust's AST in a more convenient | |
22 | /// manner (and with prettier names) before cleaning. | |
23 | #[derive(Debug)] | |
923072b8 FG |
24 | pub(crate) struct Module<'hir> { |
25 | pub(crate) name: Symbol, | |
26 | pub(crate) where_inner: Span, | |
27 | pub(crate) mods: Vec<Module<'hir>>, | |
28 | pub(crate) id: hir::HirId, | |
3c0e092e | 29 | // (item, renamed) |
923072b8 FG |
30 | pub(crate) items: Vec<(&'hir hir::Item<'hir>, Option<Symbol>)>, |
31 | pub(crate) foreigns: Vec<(&'hir hir::ForeignItem<'hir>, Option<Symbol>)>, | |
3c0e092e XL |
32 | } |
33 | ||
a2a8927a | 34 | impl Module<'_> { |
923072b8 | 35 | pub(crate) fn new(name: Symbol, id: hir::HirId, where_inner: Span) -> Self { |
3c0e092e XL |
36 | Module { name, id, where_inner, mods: Vec::new(), items: Vec::new(), foreigns: Vec::new() } |
37 | } | |
38 | ||
923072b8 | 39 | pub(crate) fn where_outer(&self, tcx: TyCtxt<'_>) -> Span { |
3c0e092e XL |
40 | tcx.hir().span(self.id) |
41 | } | |
42 | } | |
9fa01778 | 43 | |
416331ca | 44 | // FIXME: Should this be replaced with tcx.def_path_str? |
5099ac24 FG |
45 | fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<Symbol> { |
46 | let crate_name = tcx.crate_name(did.krate); | |
47 | let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name()); | |
416331ca XL |
48 | std::iter::once(crate_name).chain(relative).collect() |
49 | } | |
1a4d82fc | 50 | |
923072b8 | 51 | pub(crate) fn inherits_doc_hidden(tcx: TyCtxt<'_>, mut node: hir::HirId) -> bool { |
6a06907d XL |
52 | while let Some(id) = tcx.hir().get_enclosing_scope(node) { |
53 | node = id; | |
54 | if tcx.hir().attrs(node).lists(sym::doc).has_word(sym::hidden) { | |
55 | return true; | |
56 | } | |
57 | } | |
58 | false | |
59 | } | |
60 | ||
0731742a XL |
61 | // Also, is there some reason that this doesn't use the 'visit' |
62 | // framework from syntax?. | |
1a4d82fc | 63 | |
923072b8 | 64 | pub(crate) struct RustdocVisitor<'a, 'tcx> { |
e1599b0c | 65 | cx: &'a mut core::DocContext<'tcx>, |
48663c56 | 66 | view_item_stack: FxHashSet<hir::HirId>, |
476ff2be | 67 | inlining: bool, |
0731742a | 68 | /// Are the current module and all of its parents public? |
476ff2be | 69 | inside_public_path: bool, |
5099ac24 | 70 | exact_paths: FxHashMap<DefId, Vec<Symbol>>, |
1a4d82fc JJ |
71 | } |
72 | ||
532ac7d7 | 73 | impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { |
923072b8 | 74 | pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> { |
2c00a5a8 | 75 | // If the root is re-exported, terminate all recursion. |
0bf4aa26 | 76 | let mut stack = FxHashSet::default(); |
48663c56 | 77 | stack.insert(hir::CRATE_HIR_ID); |
1a4d82fc | 78 | RustdocVisitor { |
3b2f2976 | 79 | cx, |
1a4d82fc | 80 | view_item_stack: stack, |
476ff2be SL |
81 | inlining: false, |
82 | inside_public_path: true, | |
416331ca | 83 | exact_paths: FxHashMap::default(), |
1a4d82fc JJ |
84 | } |
85 | } | |
86 | ||
0531ce1d | 87 | fn store_path(&mut self, did: DefId) { |
416331ca XL |
88 | let tcx = self.cx.tcx; |
89 | self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did)); | |
9cc50fc6 SL |
90 | } |
91 | ||
923072b8 | 92 | pub(crate) fn visit(mut self) -> Module<'tcx> { |
5869c6ff | 93 | let mut top_level_module = self.visit_mod_contents( |
dfeec247 | 94 | hir::CRATE_HIR_ID, |
c295e0f8 | 95 | self.cx.tcx.hir().root_module(), |
17df50a5 | 96 | self.cx.tcx.crate_name(LOCAL_CRATE), |
dc9dc135 | 97 | ); |
94222f64 XL |
98 | |
99 | // `#[macro_export] macro_rules!` items are reexported at the top level of the | |
100 | // crate, regardless of where they're defined. We want to document the | |
101 | // top level rexport of the macro, not its original definition, since | |
102 | // the rexport defines the path that a user will actually see. Accordingly, | |
103 | // we add the rexport as an item here, and then skip over the original | |
104 | // definition in `visit_item()` below. | |
dc3f5686 XL |
105 | // |
106 | // We also skip `#[macro_export] macro_rules!` that have already been inserted, | |
107 | // it can happen if within the same module a `#[macro_export] macro_rules!` | |
108 | // is declared but also a reexport of itself producing two exports of the same | |
109 | // macro in the same module. | |
110 | let mut inserted = FxHashSet::default(); | |
5099ac24 | 111 | for export in self.cx.tcx.module_reexports(CRATE_DEF_ID).unwrap_or(&[]) { |
94222f64 XL |
112 | if let Res::Def(DefKind::Macro(_), def_id) = export.res { |
113 | if let Some(local_def_id) = def_id.as_local() { | |
114 | if self.cx.tcx.has_attr(def_id, sym::macro_export) { | |
dc3f5686 | 115 | if inserted.insert(def_id) { |
a2a8927a | 116 | let item = self.cx.tcx.hir().expect_item(local_def_id); |
dc3f5686 XL |
117 | top_level_module.items.push((item, None)); |
118 | } | |
94222f64 | 119 | } |
5869c6ff XL |
120 | } |
121 | } | |
5869c6ff | 122 | } |
c295e0f8 XL |
123 | |
124 | self.cx.cache.hidden_cfg = self | |
125 | .cx | |
126 | .tcx | |
127 | .hir() | |
128 | .attrs(CRATE_HIR_ID) | |
129 | .iter() | |
130 | .filter(|attr| attr.has_name(sym::doc)) | |
131 | .flat_map(|attr| attr.meta_item_list().into_iter().flatten()) | |
132 | .filter(|attr| attr.has_name(sym::cfg_hide)) | |
133 | .flat_map(|attr| { | |
134 | attr.meta_item_list() | |
135 | .unwrap_or(&[]) | |
136 | .iter() | |
137 | .filter_map(|attr| { | |
138 | Cfg::parse(attr.meta_item()?) | |
139 | .map_err(|e| self.cx.sess().diagnostic().span_err(e.span, e.msg)) | |
140 | .ok() | |
141 | }) | |
142 | .collect::<Vec<_>>() | |
143 | }) | |
923072b8 FG |
144 | .chain( |
145 | [Cfg::Cfg(sym::test, None), Cfg::Cfg(sym::doc, None), Cfg::Cfg(sym::doctest, None)] | |
146 | .into_iter(), | |
147 | ) | |
c295e0f8 XL |
148 | .collect(); |
149 | ||
6a06907d | 150 | self.cx.cache.exact_paths = self.exact_paths; |
5869c6ff | 151 | top_level_module |
1a4d82fc JJ |
152 | } |
153 | ||
dfeec247 XL |
154 | fn visit_mod_contents( |
155 | &mut self, | |
dfeec247 XL |
156 | id: hir::HirId, |
157 | m: &'tcx hir::Mod<'tcx>, | |
cdc7bbd5 | 158 | name: Symbol, |
dfeec247 | 159 | ) -> Module<'tcx> { |
04454e1e | 160 | let mut om = Module::new(name, id, m.spans.inner_span); |
3c0e092e | 161 | let def_id = self.cx.tcx.hir().local_def_id(id).to_def_id(); |
476ff2be SL |
162 | // Keep track of if there were any private modules in the path. |
163 | let orig_inside_public_path = self.inside_public_path; | |
3c0e092e | 164 | self.inside_public_path &= self.cx.tcx.visibility(def_id).is_public(); |
6a06907d XL |
165 | for &i in m.item_ids { |
166 | let item = self.cx.tcx.hir().item(i); | |
f2b60f7d FG |
167 | if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { |
168 | continue; | |
169 | } | |
92a42be0 | 170 | self.visit_item(item, None, &mut om); |
1a4d82fc | 171 | } |
f2b60f7d FG |
172 | for &i in m.item_ids { |
173 | let item = self.cx.tcx.hir().item(i); | |
174 | // To match the way import precedence works, visit glob imports last. | |
175 | // Later passes in rustdoc will de-duplicate by name and kind, so if glob- | |
176 | // imported items appear last, then they'll be the ones that get discarded. | |
177 | if matches!(item.kind, hir::ItemKind::Use(_, hir::UseKind::Glob)) { | |
178 | self.visit_item(item, None, &mut om); | |
179 | } | |
180 | } | |
476ff2be | 181 | self.inside_public_path = orig_inside_public_path; |
476ff2be | 182 | om |
1a4d82fc JJ |
183 | } |
184 | ||
cdc7bbd5 | 185 | /// Tries to resolve the target of a `pub use` statement and inlines the |
54a0048b SL |
186 | /// target if it is defined locally and would not be documented otherwise, |
187 | /// or when it is specifically requested with `please_inline`. | |
188 | /// (the latter is the case when the import is marked `doc(inline)`) | |
189 | /// | |
190 | /// Cross-crate inlining occurs later on during crate cleaning | |
191 | /// and follows different rules. | |
192 | /// | |
9fa01778 | 193 | /// Returns `true` if the target has been inlined. |
dfeec247 XL |
194 | fn maybe_inline_local( |
195 | &mut self, | |
196 | id: hir::HirId, | |
197 | res: Res, | |
fc512014 | 198 | renamed: Option<Symbol>, |
dfeec247 XL |
199 | glob: bool, |
200 | om: &mut Module<'tcx>, | |
201 | please_inline: bool, | |
202 | ) -> bool { | |
48663c56 | 203 | debug!("maybe_inline_local res: {:?}", res); |
cc61c64b | 204 | |
064997fb FG |
205 | if self.cx.output_format.is_json() { |
206 | return false; | |
207 | } | |
208 | ||
476ff2be | 209 | let tcx = self.cx.tcx; |
5099ac24 | 210 | let Some(res_did) = res.opt_def_id() else { |
476ff2be | 211 | return false; |
9fa01778 | 212 | }; |
54a0048b | 213 | |
dc9dc135 | 214 | let use_attrs = tcx.hir().attrs(id); |
0731742a | 215 | // Don't inline `doc(hidden)` imports so they can be stripped at a later stage. |
dfeec247 XL |
216 | let is_no_inline = use_attrs.lists(sym::doc).has_word(sym::no_inline) |
217 | || use_attrs.lists(sym::doc).has_word(sym::hidden); | |
a7813a04 XL |
218 | |
219 | // For cross-crate impl inlining we need to know whether items are | |
cdc7bbd5 | 220 | // reachable in documentation -- a previously unreachable item can be |
a7813a04 | 221 | // made reachable by cross-crate inlining which we're checking here. |
0731742a | 222 | // (this is done here because we need to know this upfront). |
48663c56 XL |
223 | if !res_did.is_local() && !is_no_inline { |
224 | let attrs = clean::inline::load_attrs(self.cx, res_did); | |
225 | let self_is_hidden = attrs.lists(sym::doc).has_word(sym::hidden); | |
3dfed10e XL |
226 | if !self_is_hidden { |
227 | if let Res::Def(kind, did) = res { | |
228 | if kind == DefKind::Mod { | |
229 | crate::visit_lib::LibEmbargoVisitor::new(self.cx).visit_mod(did) | |
230 | } else { | |
231 | // All items need to be handled here in case someone wishes to link | |
232 | // to them with intra-doc links | |
2b03887a FG |
233 | self.cx.cache.effective_visibilities.set_public_at_level( |
234 | did, | |
235 | || Visibility::Restricted(CRATE_DEF_ID), | |
236 | Level::Direct, | |
237 | ); | |
dfeec247 XL |
238 | } |
239 | } | |
a7813a04 | 240 | } |
dfeec247 | 241 | return false; |
a7813a04 XL |
242 | } |
243 | ||
f9f354fc | 244 | let res_hir_id = match res_did.as_local() { |
3dfed10e | 245 | Some(n) => tcx.hir().local_def_id_to_hir_id(n), |
dfeec247 | 246 | None => return false, |
a7813a04 | 247 | }; |
54a0048b | 248 | |
2b03887a | 249 | let is_private = !self.cx.cache.effective_visibilities.is_directly_public(res_did); |
6a06907d | 250 | let is_hidden = inherits_doc_hidden(self.cx.tcx, res_hir_id); |
54a0048b | 251 | |
0731742a | 252 | // Only inline if requested or if the item would otherwise be stripped. |
54a0048b | 253 | if (!please_inline && !is_private && !is_hidden) || is_no_inline { |
dfeec247 | 254 | return false; |
1a4d82fc | 255 | } |
54a0048b | 256 | |
dfeec247 XL |
257 | if !self.view_item_stack.insert(res_hir_id) { |
258 | return false; | |
259 | } | |
1a4d82fc | 260 | |
dc9dc135 | 261 | let ret = match tcx.hir().get(res_hir_id) { |
e74abb32 | 262 | Node::Item(&hir::Item { kind: hir::ItemKind::Mod(ref m), .. }) if glob => { |
476ff2be | 263 | let prev = mem::replace(&mut self.inlining, true); |
6a06907d XL |
264 | for &i in m.item_ids { |
265 | let i = self.cx.tcx.hir().item(i); | |
041b39d2 | 266 | self.visit_item(i, None, om); |
1a4d82fc | 267 | } |
476ff2be | 268 | self.inlining = prev; |
041b39d2 XL |
269 | true |
270 | } | |
b7449926 | 271 | Node::Item(it) if !glob => { |
041b39d2 XL |
272 | let prev = mem::replace(&mut self.inlining, true); |
273 | self.visit_item(it, renamed, om); | |
274 | self.inlining = prev; | |
1a4d82fc JJ |
275 | true |
276 | } | |
b7449926 | 277 | Node::ForeignItem(it) if !glob => { |
dc9dc135 XL |
278 | let prev = mem::replace(&mut self.inlining, true); |
279 | self.visit_foreign_item(it, renamed, om); | |
280 | self.inlining = prev; | |
ff7c6d11 XL |
281 | true |
282 | } | |
1a4d82fc JJ |
283 | _ => false, |
284 | }; | |
48663c56 | 285 | self.view_item_stack.remove(&res_hir_id); |
c30ab7b3 | 286 | ret |
1a4d82fc JJ |
287 | } |
288 | ||
f035d41b XL |
289 | fn visit_item( |
290 | &mut self, | |
291 | item: &'tcx hir::Item<'_>, | |
fc512014 | 292 | renamed: Option<Symbol>, |
f035d41b XL |
293 | om: &mut Module<'tcx>, |
294 | ) { | |
416331ca | 295 | debug!("visiting item {:?}", item); |
fc512014 | 296 | let name = renamed.unwrap_or(item.ident.name); |
0531ce1d | 297 | |
2b03887a | 298 | let def_id = item.owner_id.to_def_id(); |
3c0e092e | 299 | let is_pub = self.cx.tcx.visibility(def_id).is_public(); |
94222f64 XL |
300 | |
301 | if is_pub { | |
2b03887a | 302 | self.store_path(item.owner_id.to_def_id()); |
0531ce1d XL |
303 | } |
304 | ||
e74abb32 | 305 | match item.kind { |
fc512014 XL |
306 | hir::ItemKind::ForeignMod { items, .. } => { |
307 | for item in items { | |
308 | let item = self.cx.tcx.hir().foreign_item(item.id); | |
dc9dc135 XL |
309 | self.visit_foreign_item(item, None, om); |
310 | } | |
476ff2be | 311 | } |
923072b8 FG |
312 | // If we're inlining, skip private items or item reexported as "_". |
313 | _ if self.inlining && (!is_pub || renamed == Some(kw::Underscore)) => {} | |
8faf50e0 | 314 | hir::ItemKind::GlobalAsm(..) => {} |
8faf50e0 | 315 | hir::ItemKind::Use(_, hir::UseKind::ListStem) => {} |
3c0e092e | 316 | hir::ItemKind::Use(path, kind) => { |
476ff2be SL |
317 | let is_glob = kind == hir::UseKind::Glob; |
318 | ||
0731742a XL |
319 | // Struct and variant constructors and proc macro stubs always show up alongside |
320 | // their definitions, we've already processed them so just discard these. | |
416331ca XL |
321 | if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = path.res { |
322 | return; | |
94b46f34 XL |
323 | } |
324 | ||
6a06907d XL |
325 | let attrs = self.cx.tcx.hir().attrs(item.hir_id()); |
326 | ||
476ff2be SL |
327 | // If there was a private module in the current path then don't bother inlining |
328 | // anything as it will probably be stripped anyway. | |
94222f64 | 329 | if is_pub && self.inside_public_path { |
6a06907d | 330 | let please_inline = attrs.iter().any(|item| match item.meta_item_list() { |
3dfed10e XL |
331 | Some(ref list) if item.has_name(sym::doc) => { |
332 | list.iter().any(|i| i.has_name(sym::inline)) | |
85aaf69f | 333 | } |
dfeec247 | 334 | _ => false, |
85aaf69f | 335 | }); |
fc512014 | 336 | let ident = if is_glob { None } else { Some(name) }; |
dfeec247 | 337 | if self.maybe_inline_local( |
6a06907d | 338 | item.hir_id(), |
dfeec247 XL |
339 | path.res, |
340 | ident, | |
341 | is_glob, | |
342 | om, | |
343 | please_inline, | |
344 | ) { | |
476ff2be | 345 | return; |
85aaf69f | 346 | } |
476ff2be SL |
347 | } |
348 | ||
5869c6ff | 349 | om.items.push((item, renamed)) |
85aaf69f | 350 | } |
5e7ed085 FG |
351 | hir::ItemKind::Macro(ref macro_def, _) => { |
352 | // `#[macro_export] macro_rules!` items are handled separately in `visit()`, | |
94222f64 XL |
353 | // above, since they need to be documented at the module top level. Accordingly, |
354 | // we only want to handle macros if one of three conditions holds: | |
355 | // | |
356 | // 1. This macro was defined by `macro`, and thus isn't covered by the case | |
357 | // above. | |
358 | // 2. This macro isn't marked with `#[macro_export]`, and thus isn't covered | |
359 | // by the case above. | |
360 | // 3. We're inlining, since a reexport where inlining has been requested | |
361 | // should be inlined even if it is also documented at the top level. | |
362 | ||
2b03887a | 363 | let def_id = item.owner_id.to_def_id(); |
94222f64 XL |
364 | let is_macro_2_0 = !macro_def.macro_rules; |
365 | let nonexported = !self.cx.tcx.has_attr(def_id, sym::macro_export); | |
366 | ||
367 | if is_macro_2_0 || nonexported || self.inlining { | |
368 | om.items.push((item, renamed)); | |
369 | } | |
370 | } | |
8faf50e0 | 371 | hir::ItemKind::Mod(ref m) => { |
3c0e092e | 372 | om.mods.push(self.visit_mod_contents(item.hir_id(), m, name)); |
dfeec247 | 373 | } |
fc512014 XL |
374 | hir::ItemKind::Fn(..) |
375 | | hir::ItemKind::ExternCrate(..) | |
376 | | hir::ItemKind::Enum(..) | |
377 | | hir::ItemKind::Struct(..) | |
378 | | hir::ItemKind::Union(..) | |
379 | | hir::ItemKind::TyAlias(..) | |
380 | | hir::ItemKind::OpaqueTy(..) | |
381 | | hir::ItemKind::Static(..) | |
382 | | hir::ItemKind::Trait(..) | |
383 | | hir::ItemKind::TraitAlias(..) => om.items.push((item, renamed)), | |
384 | hir::ItemKind::Const(..) => { | |
dfeec247 XL |
385 | // Underscore constants do not correspond to a nameable item and |
386 | // so are never useful in documentation. | |
fc512014 XL |
387 | if name != kw::Underscore { |
388 | om.items.push((item, renamed)); | |
dfeec247 XL |
389 | } |
390 | } | |
923072b8 | 391 | hir::ItemKind::Impl(impl_) => { |
0bf4aa26 XL |
392 | // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick |
393 | // them up regardless of where they're located. | |
5869c6ff | 394 | if !self.inlining && impl_.of_trait.is_none() { |
fc512014 | 395 | om.items.push((item, None)); |
9346a6ac | 396 | } |
dfeec247 | 397 | } |
1a4d82fc JJ |
398 | } |
399 | } | |
400 | ||
dfeec247 XL |
401 | fn visit_foreign_item( |
402 | &mut self, | |
f035d41b | 403 | item: &'tcx hir::ForeignItem<'_>, |
fc512014 | 404 | renamed: Option<Symbol>, |
dfeec247 XL |
405 | om: &mut Module<'tcx>, |
406 | ) { | |
dc9dc135 | 407 | // If inlining we only want to include public functions. |
2b03887a | 408 | if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() { |
fc512014 | 409 | om.foreigns.push((item, renamed)); |
1a4d82fc JJ |
410 | } |
411 | } | |
412 | } |