1 //! The Rust AST Visitor. Extracts useful information and massages it into a form
2 //! usable for `clean`.
4 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
6 use rustc_hir
::def
::{DefKind, Res}
;
7 use rustc_hir
::def_id
::DefId
;
9 use rustc_hir
::CRATE_HIR_ID
;
10 use rustc_middle
::middle
::privacy
::AccessLevel
;
11 use rustc_middle
::ty
::TyCtxt
;
12 use rustc_span
::def_id
::{CRATE_DEF_ID, LOCAL_CRATE}
;
13 use rustc_span
::symbol
::{kw, sym, Symbol}
;
18 use crate::clean
::{self, cfg::Cfg, AttributesExt, NestedAttributesExt}
;
21 /// This module is used to store stuff from Rust's AST in a more convenient
22 /// manner (and with prettier names) before cleaning.
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
,
30 pub(crate) items
: Vec
<(&'hir hir
::Item
<'hir
>, Option
<Symbol
>)>,
31 pub(crate) foreigns
: Vec
<(&'hir hir
::ForeignItem
<'hir
>, Option
<Symbol
>)>,
35 pub(crate) fn new(name
: Symbol
, id
: hir
::HirId
, where_inner
: Span
) -> Self {
36 Module { name, id, where_inner, mods: Vec::new(), items: Vec::new(), foreigns: Vec::new() }
39 pub(crate) fn where_outer(&self, tcx
: TyCtxt
<'_
>) -> Span
{
40 tcx
.hir().span(self.id
)
44 // FIXME: Should this be replaced with tcx.def_path_str?
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());
48 std
::iter
::once(crate_name
).chain(relative
).collect()
51 pub(crate) fn inherits_doc_hidden(tcx
: TyCtxt
<'_
>, mut node
: hir
::HirId
) -> bool
{
52 while let Some(id
) = tcx
.hir().get_enclosing_scope(node
) {
54 if tcx
.hir().attrs(node
).lists(sym
::doc
).has_word(sym
::hidden
) {
61 // Also, is there some reason that this doesn't use the 'visit'
62 // framework from syntax?.
64 pub(crate) struct RustdocVisitor
<'a
, 'tcx
> {
65 cx
: &'a
mut core
::DocContext
<'tcx
>,
66 view_item_stack
: FxHashSet
<hir
::HirId
>,
68 /// Are the current module and all of its parents public?
69 inside_public_path
: bool
,
70 exact_paths
: FxHashMap
<DefId
, Vec
<Symbol
>>,
73 impl<'a
, 'tcx
> RustdocVisitor
<'a
, 'tcx
> {
74 pub(crate) fn new(cx
: &'a
mut core
::DocContext
<'tcx
>) -> RustdocVisitor
<'a
, 'tcx
> {
75 // If the root is re-exported, terminate all recursion.
76 let mut stack
= FxHashSet
::default();
77 stack
.insert(hir
::CRATE_HIR_ID
);
80 view_item_stack
: stack
,
82 inside_public_path
: true,
83 exact_paths
: FxHashMap
::default(),
87 fn store_path(&mut self, did
: DefId
) {
88 let tcx
= self.cx
.tcx
;
89 self.exact_paths
.entry(did
).or_insert_with(|| def_id_to_path(tcx
, did
));
92 pub(crate) fn visit(mut self) -> Module
<'tcx
> {
93 let mut top_level_module
= self.visit_mod_contents(
95 self.cx
.tcx
.hir().root_module(),
96 self.cx
.tcx
.crate_name(LOCAL_CRATE
),
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.
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();
111 for export
in self.cx
.tcx
.module_reexports(CRATE_DEF_ID
).unwrap_or(&[]) {
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
) {
115 if inserted
.insert(def_id
) {
116 let item
= self.cx
.tcx
.hir().expect_item(local_def_id
);
117 top_level_module
.items
.push((item
, None
));
124 self.cx
.cache
.hidden_cfg
= self
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
))
134 attr
.meta_item_list()
138 Cfg
::parse(attr
.meta_item()?
)
139 .map_err(|e
| self.cx
.sess().diagnostic().span_err(e
.span
, e
.msg
))
145 [Cfg
::Cfg(sym
::test
, None
), Cfg
::Cfg(sym
::doc
, None
), Cfg
::Cfg(sym
::doctest
, None
)]
150 self.cx
.cache
.exact_paths
= self.exact_paths
;
154 fn visit_mod_contents(
157 m
: &'tcx hir
::Mod
<'tcx
>,
160 let mut om
= Module
::new(name
, id
, m
.spans
.inner_span
);
161 let def_id
= self.cx
.tcx
.hir().local_def_id(id
).to_def_id();
162 // Keep track of if there were any private modules in the path.
163 let orig_inside_public_path
= self.inside_public_path
;
164 self.inside_public_path
&= self.cx
.tcx
.visibility(def_id
).is_public();
165 for &i
in m
.item_ids
{
166 let item
= self.cx
.tcx
.hir().item(i
);
167 if matches
!(item
.kind
, hir
::ItemKind
::Use(_
, hir
::UseKind
::Glob
)) {
170 self.visit_item(item
, None
, &mut om
);
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
);
181 self.inside_public_path
= orig_inside_public_path
;
185 /// Tries to resolve the target of a `pub use` statement and inlines the
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)`)
190 /// Cross-crate inlining occurs later on during crate cleaning
191 /// and follows different rules.
193 /// Returns `true` if the target has been inlined.
194 fn maybe_inline_local(
198 renamed
: Option
<Symbol
>,
200 om
: &mut Module
<'tcx
>,
203 debug
!("maybe_inline_local res: {:?}", res
);
205 if self.cx
.output_format
.is_json() {
209 let tcx
= self.cx
.tcx
;
210 let Some(res_did
) = res
.opt_def_id() else {
214 let use_attrs
= tcx
.hir().attrs(id
);
215 // Don't inline `doc(hidden)` imports so they can be stripped at a later stage.
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
);
219 // For cross-crate impl inlining we need to know whether items are
220 // reachable in documentation -- a previously unreachable item can be
221 // made reachable by cross-crate inlining which we're checking here.
222 // (this is done here because we need to know this upfront).
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
);
227 if let Res
::Def(kind
, did
) = res
{
228 if kind
== DefKind
::Mod
{
229 crate::visit_lib
::LibEmbargoVisitor
::new(self.cx
).visit_mod(did
)
231 // All items need to be handled here in case someone wishes to link
232 // to them with intra-doc links
233 self.cx
.cache
.access_levels
.map
.insert(did
, AccessLevel
::Public
);
240 let res_hir_id
= match res_did
.as_local() {
241 Some(n
) => tcx
.hir().local_def_id_to_hir_id(n
),
242 None
=> return false,
245 let is_private
= !self.cx
.cache
.access_levels
.is_public(res_did
);
246 let is_hidden
= inherits_doc_hidden(self.cx
.tcx
, res_hir_id
);
248 // Only inline if requested or if the item would otherwise be stripped.
249 if (!please_inline
&& !is_private
&& !is_hidden
) || is_no_inline
{
253 if !self.view_item_stack
.insert(res_hir_id
) {
257 let ret
= match tcx
.hir().get(res_hir_id
) {
258 Node
::Item(&hir
::Item { kind: hir::ItemKind::Mod(ref m), .. }
) if glob
=> {
259 let prev
= mem
::replace(&mut self.inlining
, true);
260 for &i
in m
.item_ids
{
261 let i
= self.cx
.tcx
.hir().item(i
);
262 self.visit_item(i
, None
, om
);
264 self.inlining
= prev
;
267 Node
::Item(it
) if !glob
=> {
268 let prev
= mem
::replace(&mut self.inlining
, true);
269 self.visit_item(it
, renamed
, om
);
270 self.inlining
= prev
;
273 Node
::ForeignItem(it
) if !glob
=> {
274 let prev
= mem
::replace(&mut self.inlining
, true);
275 self.visit_foreign_item(it
, renamed
, om
);
276 self.inlining
= prev
;
281 self.view_item_stack
.remove(&res_hir_id
);
287 item
: &'tcx hir
::Item
<'_
>,
288 renamed
: Option
<Symbol
>,
289 om
: &mut Module
<'tcx
>,
291 debug
!("visiting item {:?}", item
);
292 let name
= renamed
.unwrap_or(item
.ident
.name
);
294 let def_id
= item
.def_id
.to_def_id();
295 let is_pub
= self.cx
.tcx
.visibility(def_id
).is_public();
298 self.store_path(item
.def_id
.to_def_id());
302 hir
::ItemKind
::ForeignMod { items, .. }
=> {
304 let item
= self.cx
.tcx
.hir().foreign_item(item
.id
);
305 self.visit_foreign_item(item
, None
, om
);
308 // If we're inlining, skip private items or item reexported as "_".
309 _
if self.inlining
&& (!is_pub
|| renamed
== Some(kw
::Underscore
)) => {}
310 hir
::ItemKind
::GlobalAsm(..) => {}
311 hir
::ItemKind
::Use(_
, hir
::UseKind
::ListStem
) => {}
312 hir
::ItemKind
::Use(path
, kind
) => {
313 let is_glob
= kind
== hir
::UseKind
::Glob
;
315 // Struct and variant constructors and proc macro stubs always show up alongside
316 // their definitions, we've already processed them so just discard these.
317 if let Res
::Def(DefKind
::Ctor(..), _
) | Res
::SelfCtor(..) = path
.res
{
321 let attrs
= self.cx
.tcx
.hir().attrs(item
.hir_id());
323 // If there was a private module in the current path then don't bother inlining
324 // anything as it will probably be stripped anyway.
325 if is_pub
&& self.inside_public_path
{
326 let please_inline
= attrs
.iter().any(|item
| match item
.meta_item_list() {
327 Some(ref list
) if item
.has_name(sym
::doc
) => {
328 list
.iter().any(|i
| i
.has_name(sym
::inline
))
332 let ident
= if is_glob { None }
else { Some(name) }
;
333 if self.maybe_inline_local(
345 om
.items
.push((item
, renamed
))
347 hir
::ItemKind
::Macro(ref macro_def
, _
) => {
348 // `#[macro_export] macro_rules!` items are handled separately in `visit()`,
349 // above, since they need to be documented at the module top level. Accordingly,
350 // we only want to handle macros if one of three conditions holds:
352 // 1. This macro was defined by `macro`, and thus isn't covered by the case
354 // 2. This macro isn't marked with `#[macro_export]`, and thus isn't covered
355 // by the case above.
356 // 3. We're inlining, since a reexport where inlining has been requested
357 // should be inlined even if it is also documented at the top level.
359 let def_id
= item
.def_id
.to_def_id();
360 let is_macro_2_0
= !macro_def
.macro_rules
;
361 let nonexported
= !self.cx
.tcx
.has_attr(def_id
, sym
::macro_export
);
363 if is_macro_2_0
|| nonexported
|| self.inlining
{
364 om
.items
.push((item
, renamed
));
367 hir
::ItemKind
::Mod(ref m
) => {
368 om
.mods
.push(self.visit_mod_contents(item
.hir_id(), m
, name
));
370 hir
::ItemKind
::Fn(..)
371 | hir
::ItemKind
::ExternCrate(..)
372 | hir
::ItemKind
::Enum(..)
373 | hir
::ItemKind
::Struct(..)
374 | hir
::ItemKind
::Union(..)
375 | hir
::ItemKind
::TyAlias(..)
376 | hir
::ItemKind
::OpaqueTy(..)
377 | hir
::ItemKind
::Static(..)
378 | hir
::ItemKind
::Trait(..)
379 | hir
::ItemKind
::TraitAlias(..) => om
.items
.push((item
, renamed
)),
380 hir
::ItemKind
::Const(..) => {
381 // Underscore constants do not correspond to a nameable item and
382 // so are never useful in documentation.
383 if name
!= kw
::Underscore
{
384 om
.items
.push((item
, renamed
));
387 hir
::ItemKind
::Impl(impl_
) => {
388 // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick
389 // them up regardless of where they're located.
390 if !self.inlining
&& impl_
.of_trait
.is_none() {
391 om
.items
.push((item
, None
));
397 fn visit_foreign_item(
399 item
: &'tcx hir
::ForeignItem
<'_
>,
400 renamed
: Option
<Symbol
>,
401 om
: &mut Module
<'tcx
>,
403 // If inlining we only want to include public functions.
404 if !self.inlining
|| self.cx
.tcx
.visibility(item
.def_id
).is_public() {
405 om
.foreigns
.push((item
, renamed
));