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_middle
::middle
::privacy
::AccessLevel
;
10 use rustc_middle
::ty
::TyCtxt
;
11 use rustc_span
::source_map
::Spanned
;
12 use rustc_span
::symbol
::{kw, sym, Symbol}
;
13 use rustc_span
::{self, Span}
;
17 use crate::clean
::{self, AttributesExt, NestedAttributesExt}
;
19 use crate::doctree
::*;
21 // FIXME: Should this be replaced with tcx.def_path_str?
22 fn def_id_to_path(tcx
: TyCtxt
<'_
>, did
: DefId
) -> Vec
<String
> {
23 let crate_name
= tcx
.crate_name(did
.krate
).to_string();
24 let relative
= tcx
.def_path(did
).data
.into_iter().filter_map(|elem
| {
25 // extern blocks have an empty name
26 let s
= elem
.data
.to_string();
27 if !s
.is_empty() { Some(s) }
else { None }
29 std
::iter
::once(crate_name
).chain(relative
).collect()
32 crate fn inherits_doc_hidden(tcx
: TyCtxt
<'_
>, mut node
: hir
::HirId
) -> bool
{
33 while let Some(id
) = tcx
.hir().get_enclosing_scope(node
) {
35 if tcx
.hir().attrs(node
).lists(sym
::doc
).has_word(sym
::hidden
) {
42 // Also, is there some reason that this doesn't use the 'visit'
43 // framework from syntax?.
45 crate struct RustdocVisitor
<'a
, 'tcx
> {
46 cx
: &'a
mut core
::DocContext
<'tcx
>,
47 view_item_stack
: FxHashSet
<hir
::HirId
>,
49 /// Are the current module and all of its parents public?
50 inside_public_path
: bool
,
51 exact_paths
: FxHashMap
<DefId
, Vec
<String
>>,
54 impl<'a
, 'tcx
> RustdocVisitor
<'a
, 'tcx
> {
55 crate fn new(cx
: &'a
mut core
::DocContext
<'tcx
>) -> RustdocVisitor
<'a
, 'tcx
> {
56 // If the root is re-exported, terminate all recursion.
57 let mut stack
= FxHashSet
::default();
58 stack
.insert(hir
::CRATE_HIR_ID
);
61 view_item_stack
: stack
,
63 inside_public_path
: true,
64 exact_paths
: FxHashMap
::default(),
68 fn store_path(&mut self, did
: DefId
) {
69 let tcx
= self.cx
.tcx
;
70 self.exact_paths
.entry(did
).or_insert_with(|| def_id_to_path(tcx
, did
));
73 crate fn visit(mut self, krate
: &'tcx hir
::Crate
<'_
>) -> Module
<'tcx
> {
74 let span
= krate
.item
.inner
;
75 let mut top_level_module
= self.visit_mod_contents(
77 &Spanned { span, node: hir::VisibilityKind::Public }
,
80 self.cx
.tcx
.crate_name
,
82 // Attach the crate's exported macros to the top-level module.
83 // In the case of macros 2.0 (`pub macro`), and for built-in `derive`s or attributes as
84 // well (_e.g._, `Copy`), these are wrongly bundled in there too, so we need to fix that by
85 // moving them back to their correct locations.
86 'exported_macros
: for def
in krate
.exported_macros
{
87 // The `def` of a macro in `exported_macros` should correspond to either:
88 // - a `#[macro_export] macro_rules!` macro,
89 // - a built-in `derive` (or attribute) macro such as the ones in `::core`,
91 // Only the last two need to be fixed, thus:
92 if def
.ast
.macro_rules
{
93 top_level_module
.macros
.push((def
, None
));
94 continue 'exported_macros
;
96 let tcx
= self.cx
.tcx
;
97 // Note: this is not the same as `.parent_module()`. Indeed, the latter looks
98 // for the closest module _ancestor_, which is not necessarily a direct parent
99 // (since a direct parent isn't necessarily a module, c.f. #77828).
100 let macro_parent_def_id
= {
101 use rustc_middle
::ty
::DefIdTree
;
102 tcx
.parent(def
.def_id
.to_def_id()).unwrap()
104 let macro_parent_path
= tcx
.def_path(macro_parent_def_id
);
105 // HACK: rustdoc has no way to lookup `doctree::Module`s by their HirId. Instead,
106 // lookup the module by its name, by looking at each path segment one at a time.
107 let mut cur_mod
= &mut top_level_module
;
108 for path_segment
in macro_parent_path
.data
{
109 // Path segments may refer to a module (in which case they belong to the type
110 // namespace), which is _necessary_ for the macro to be accessible outside it
111 // (no "associated macros" as of yet). Else we bail with an outer `continue`.
112 let path_segment_ty_ns
= match path_segment
.data
{
113 rustc_hir
::definitions
::DefPathData
::TypeNs(symbol
) => symbol
,
114 _
=> continue 'exported_macros
,
116 // Descend into the child module that matches this path segment (if any).
117 match cur_mod
.mods
.iter_mut().find(|child
| child
.name
== path_segment_ty_ns
) {
118 Some(child_mod
) => cur_mod
= &mut *child_mod
,
119 None
=> continue 'exported_macros
,
122 let cur_mod_def_id
= tcx
.hir().local_def_id(cur_mod
.id
).to_def_id();
123 assert_eq
!(cur_mod_def_id
, macro_parent_def_id
);
124 cur_mod
.macros
.push((def
, None
));
126 self.cx
.cache
.exact_paths
= self.exact_paths
;
130 fn visit_mod_contents(
133 vis
: &hir
::Visibility
<'_
>,
135 m
: &'tcx hir
::Mod
<'tcx
>,
138 let mut om
= Module
::new(name
);
139 om
.where_outer
= span
;
140 om
.where_inner
= m
.inner
;
142 // Keep track of if there were any private modules in the path.
143 let orig_inside_public_path
= self.inside_public_path
;
144 self.inside_public_path
&= vis
.node
.is_pub();
145 for &i
in m
.item_ids
{
146 let item
= self.cx
.tcx
.hir().item(i
);
147 self.visit_item(item
, None
, &mut om
);
149 self.inside_public_path
= orig_inside_public_path
;
153 /// Tries to resolve the target of a `pub use` statement and inlines the
154 /// target if it is defined locally and would not be documented otherwise,
155 /// or when it is specifically requested with `please_inline`.
156 /// (the latter is the case when the import is marked `doc(inline)`)
158 /// Cross-crate inlining occurs later on during crate cleaning
159 /// and follows different rules.
161 /// Returns `true` if the target has been inlined.
162 fn maybe_inline_local(
166 renamed
: Option
<Symbol
>,
168 om
: &mut Module
<'tcx
>,
171 debug
!("maybe_inline_local res: {:?}", res
);
173 let tcx
= self.cx
.tcx
;
174 let res_did
= if let Some(did
) = res
.opt_def_id() {
180 let use_attrs
= tcx
.hir().attrs(id
);
181 // Don't inline `doc(hidden)` imports so they can be stripped at a later stage.
182 let is_no_inline
= use_attrs
.lists(sym
::doc
).has_word(sym
::no_inline
)
183 || use_attrs
.lists(sym
::doc
).has_word(sym
::hidden
);
185 // For cross-crate impl inlining we need to know whether items are
186 // reachable in documentation -- a previously unreachable item can be
187 // made reachable by cross-crate inlining which we're checking here.
188 // (this is done here because we need to know this upfront).
189 if !res_did
.is_local() && !is_no_inline
{
190 let attrs
= clean
::inline
::load_attrs(self.cx
, res_did
);
191 let self_is_hidden
= attrs
.lists(sym
::doc
).has_word(sym
::hidden
);
193 if let Res
::Def(kind
, did
) = res
{
194 if kind
== DefKind
::Mod
{
195 crate::visit_lib
::LibEmbargoVisitor
::new(self.cx
).visit_mod(did
)
197 // All items need to be handled here in case someone wishes to link
198 // to them with intra-doc links
199 self.cx
.cache
.access_levels
.map
.insert(did
, AccessLevel
::Public
);
206 let res_hir_id
= match res_did
.as_local() {
207 Some(n
) => tcx
.hir().local_def_id_to_hir_id(n
),
208 None
=> return false,
211 let is_private
= !self.cx
.cache
.access_levels
.is_public(res_did
);
212 let is_hidden
= inherits_doc_hidden(self.cx
.tcx
, res_hir_id
);
214 // Only inline if requested or if the item would otherwise be stripped.
215 if (!please_inline
&& !is_private
&& !is_hidden
) || is_no_inline
{
219 if !self.view_item_stack
.insert(res_hir_id
) {
223 let ret
= match tcx
.hir().get(res_hir_id
) {
224 Node
::Item(&hir
::Item { kind: hir::ItemKind::Mod(ref m), .. }
) if glob
=> {
225 let prev
= mem
::replace(&mut self.inlining
, true);
226 for &i
in m
.item_ids
{
227 let i
= self.cx
.tcx
.hir().item(i
);
228 self.visit_item(i
, None
, om
);
230 self.inlining
= prev
;
233 Node
::Item(it
) if !glob
=> {
234 let prev
= mem
::replace(&mut self.inlining
, true);
235 self.visit_item(it
, renamed
, om
);
236 self.inlining
= prev
;
239 Node
::ForeignItem(it
) if !glob
=> {
240 let prev
= mem
::replace(&mut self.inlining
, true);
241 self.visit_foreign_item(it
, renamed
, om
);
242 self.inlining
= prev
;
245 Node
::MacroDef(def
) if !glob
=> {
246 om
.macros
.push((def
, renamed
));
251 self.view_item_stack
.remove(&res_hir_id
);
257 item
: &'tcx hir
::Item
<'_
>,
258 renamed
: Option
<Symbol
>,
259 om
: &mut Module
<'tcx
>,
261 debug
!("visiting item {:?}", item
);
262 let name
= renamed
.unwrap_or(item
.ident
.name
);
264 if item
.vis
.node
.is_pub() {
265 self.store_path(item
.def_id
.to_def_id());
269 hir
::ItemKind
::ForeignMod { items, .. }
=> {
271 let item
= self.cx
.tcx
.hir().foreign_item(item
.id
);
272 self.visit_foreign_item(item
, None
, om
);
275 // If we're inlining, skip private items.
276 _
if self.inlining
&& !item
.vis
.node
.is_pub() => {}
277 hir
::ItemKind
::GlobalAsm(..) => {}
278 hir
::ItemKind
::Use(_
, hir
::UseKind
::ListStem
) => {}
279 hir
::ItemKind
::Use(ref path
, kind
) => {
280 let is_glob
= kind
== hir
::UseKind
::Glob
;
282 // Struct and variant constructors and proc macro stubs always show up alongside
283 // their definitions, we've already processed them so just discard these.
284 if let Res
::Def(DefKind
::Ctor(..), _
) | Res
::SelfCtor(..) = path
.res
{
288 let attrs
= self.cx
.tcx
.hir().attrs(item
.hir_id());
290 // If there was a private module in the current path then don't bother inlining
291 // anything as it will probably be stripped anyway.
292 if item
.vis
.node
.is_pub() && self.inside_public_path
{
293 let please_inline
= attrs
.iter().any(|item
| match item
.meta_item_list() {
294 Some(ref list
) if item
.has_name(sym
::doc
) => {
295 list
.iter().any(|i
| i
.has_name(sym
::inline
))
299 let ident
= if is_glob { None }
else { Some(name) }
;
300 if self.maybe_inline_local(
312 om
.items
.push((item
, renamed
))
314 hir
::ItemKind
::Mod(ref m
) => {
315 om
.mods
.push(self.visit_mod_contents(item
.span
, &item
.vis
, item
.hir_id(), m
, name
));
317 hir
::ItemKind
::Fn(..)
318 | hir
::ItemKind
::ExternCrate(..)
319 | hir
::ItemKind
::Enum(..)
320 | hir
::ItemKind
::Struct(..)
321 | hir
::ItemKind
::Union(..)
322 | hir
::ItemKind
::TyAlias(..)
323 | hir
::ItemKind
::OpaqueTy(..)
324 | hir
::ItemKind
::Static(..)
325 | hir
::ItemKind
::Trait(..)
326 | hir
::ItemKind
::TraitAlias(..) => om
.items
.push((item
, renamed
)),
327 hir
::ItemKind
::Const(..) => {
328 // Underscore constants do not correspond to a nameable item and
329 // so are never useful in documentation.
330 if name
!= kw
::Underscore
{
331 om
.items
.push((item
, renamed
));
334 hir
::ItemKind
::Impl(ref impl_
) => {
335 // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick
336 // them up regardless of where they're located.
337 if !self.inlining
&& impl_
.of_trait
.is_none() {
338 om
.items
.push((item
, None
));
344 fn visit_foreign_item(
346 item
: &'tcx hir
::ForeignItem
<'_
>,
347 renamed
: Option
<Symbol
>,
348 om
: &mut Module
<'tcx
>,
350 // If inlining we only want to include public functions.
351 if !self.inlining
|| item
.vis
.node
.is_pub() {
352 om
.foreigns
.push((item
, renamed
));