Skip to main content

rustdoc/
visit_ast.rs

1//! The Rust AST Visitor. Extracts useful information and massages it into a form
2//! usable for `clean`.
3
4use std::mem;
5
6use rustc_ast::attr::AttributeExt;
7use rustc_attr_ir::{DocInline, find_attr};
8use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
9use rustc_hir::def::{DefKind, MacroKinds, Res};
10use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LocalDefIdSet};
11use rustc_hir::intravisit::{Visitor, walk_body, walk_item};
12use rustc_hir::{self as hir, HirId, Node};
13use rustc_middle::hir::nested_filter;
14use rustc_middle::ty::TyCtxt;
15use rustc_span::Span;
16use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalModId};
17use rustc_span::symbol::{Symbol, kw};
18use tracing::{debug, instrument, trace};
19
20use crate::clean::reexport_chain;
21use crate::clean::utils::{inherits_doc_hidden, should_ignore_res};
22use crate::core;
23
24/// This module is used to store stuff from Rust's AST in a more convenient
25/// manner (and with prettier names) before cleaning.
26#[derive(Debug)]
27pub(crate) struct Module<'hir> {
28    pub(crate) name: Symbol,
29    pub(crate) where_inner: Span,
30    pub(crate) mods: Vec<Module<'hir>>,
31    pub(crate) def_id: LocalDefId,
32    pub(crate) renamed: Option<Symbol>,
33    pub(crate) import_id: Option<LocalDefId>,
34    /// The key is the item `ItemId`. We use `FxIndexMap` to keep the insert order.
35    ///
36    /// `import_id` needs to be a `Vec` because we live in a dark world where you can have code
37    /// like:
38    ///
39    /// ```
40    /// mod raw {
41    ///     pub fn foo() {}
42    /// }
43    ///
44    /// /// Foobar
45    /// pub use raw::foo;
46    ///
47    /// pub use raw::*;
48    /// ```
49    ///
50    /// So in this case, we don't want to have two items but just one with attributes from all
51    /// non-glob imports to be merged. Glob imports attributes are always ignored, whether they're
52    /// shadowed or not.
53    pub(crate) items: FxIndexMap<(LocalDefId, Option<Symbol>), ItemEntry<'hir>>,
54
55    /// The key is `(def_id, renamed)`.
56    ///
57    /// `inlined_foreigns` only contains `extern` items
58    /// that are cross-crate inlined.
59    ///
60    /// Locally inlined `extern` items are
61    /// stored in `foreigns` with the `import_id` set,
62    /// analogous to how `items` is.
63    pub(crate) inlined_foreigns: FxIndexMap<(DefId, Option<Symbol>), InlinedForeign>,
64    /// (item, renamed, import_id)
65    pub(crate) foreigns: Vec<Foreign<'hir>>,
66}
67
68#[derive(Debug)]
69pub(crate) struct ItemEntry<'hir> {
70    pub(crate) item: &'hir hir::Item<'hir>,
71    pub(crate) renamed: Option<Symbol>,
72    pub(crate) import_ids: Vec<LocalDefId>,
73}
74
75#[derive(Debug)]
76pub(crate) struct InlinedForeign {
77    pub(crate) res: Res,
78    pub(crate) import_id: LocalDefId,
79}
80
81#[derive(Debug)]
82pub(crate) struct Foreign<'hir> {
83    pub(crate) item: &'hir hir::ForeignItem<'hir>,
84    pub(crate) renamed: Option<Symbol>,
85    pub(crate) import_id: Option<LocalDefId>,
86}
87
88impl Module<'_> {
89    pub(crate) fn new(
90        name: Symbol,
91        def_id: LocalDefId,
92        where_inner: Span,
93        renamed: Option<Symbol>,
94        import_id: Option<LocalDefId>,
95    ) -> Self {
96        Module {
97            name,
98            def_id,
99            where_inner,
100            renamed,
101            import_id,
102            mods: Vec::new(),
103            items: FxIndexMap::default(),
104            inlined_foreigns: FxIndexMap::default(),
105            foreigns: Vec::new(),
106        }
107    }
108
109    pub(crate) fn where_outer(&self, tcx: TyCtxt<'_>) -> Span {
110        tcx.def_span(self.def_id)
111    }
112}
113
114// FIXME: Should this be replaced with tcx.def_path_str?
115fn def_id_to_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<Symbol> {
116    let crate_name = tcx.crate_name(did.krate);
117    let relative = tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name());
118    std::iter::once(crate_name).chain(relative).collect()
119}
120
121#[derive(Copy, Clone)]
122enum GlobMode {
123    // Globs and everything else
124    Everything,
125    // Skip all globs
126    NoGlob,
127    // Skip all items except for globs
128    Only,
129}
130
131pub(crate) struct RustdocVisitor<'a, 'tcx> {
132    cx: &'a mut core::DocContext<'tcx>,
133    view_item_stack: LocalDefIdSet,
134    inlining: bool,
135    /// Are the current module and all of its parents public?
136    inside_public_path: bool,
137    exact_paths: DefIdMap<Vec<Symbol>>,
138    modules: Vec<Module<'tcx>>,
139    is_importable_from_parent: bool,
140    inside_body: bool,
141    glob_mode: GlobMode,
142}
143
144impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
145    pub(crate) fn new(cx: &'a mut core::DocContext<'tcx>) -> RustdocVisitor<'a, 'tcx> {
146        // If the root is re-exported, terminate all recursion.
147        let mut stack = LocalDefIdSet::default();
148        stack.insert(CRATE_DEF_ID);
149        let om = Module::new(
150            cx.tcx.crate_name(LOCAL_CRATE),
151            CRATE_DEF_ID,
152            cx.tcx.hir_root_module().spans.inner_span,
153            None,
154            None,
155        );
156
157        RustdocVisitor {
158            cx,
159            view_item_stack: stack,
160            inlining: false,
161            inside_public_path: true,
162            exact_paths: Default::default(),
163            modules: vec![om],
164            is_importable_from_parent: true,
165            inside_body: false,
166            glob_mode: GlobMode::Everything,
167        }
168    }
169
170    fn store_path(&mut self, did: DefId) {
171        let tcx = self.cx.tcx;
172        self.exact_paths.entry(did).or_insert_with(|| def_id_to_path(tcx, did));
173    }
174
175    pub(crate) fn visit(mut self) -> Module<'tcx> {
176        let root_module = self.cx.tcx.hir_root_module();
177        self.visit_mod_contents(CRATE_DEF_ID, root_module);
178
179        let mut top_level_module = self.modules.pop().unwrap();
180
181        // `#[macro_export] macro_rules!` items are reexported at the top level of the
182        // crate, regardless of where they're defined. We want to document the
183        // top level re-export of the macro, not its original definition, since
184        // the re-export defines the path that a user will actually see. Accordingly,
185        // we add the re-export as an item here, and then skip over the original
186        // definition in `visit_item()` below.
187        //
188        // We also skip `#[macro_export] macro_rules!` that have already been inserted,
189        // it can happen if within the same module a `#[macro_export] macro_rules!`
190        // is declared but also a reexport of itself producing two exports of the same
191        // macro in the same module.
192        let mut inserted = FxHashSet::default();
193        for child in self.cx.tcx.module_children_local(CRATE_DEF_ID) {
194            if !child.reexport_chain.is_empty()
195                && let Res::Def(DefKind::Macro(_), def_id) = child.res
196                && let Some(local_def_id) = def_id.as_local()
197                && find_attr!(self.cx.tcx, def_id, MacroExport { .. })
198                && inserted.insert(def_id)
199            {
200                let item = self.cx.tcx.hir_expect_item(local_def_id);
201                let (ident, _, _) = item.expect_macro();
202                top_level_module.items.insert(
203                    (local_def_id, Some(ident.name)),
204                    ItemEntry { item, renamed: None, import_ids: Vec::new() },
205                );
206            }
207        }
208
209        self.cx.cache.exact_paths = self.exact_paths;
210        top_level_module
211    }
212
213    /// This method will go through the given module items in two passes:
214    /// 1. The items which are not glob imports/reexports.
215    /// 2. The glob imports/reexports.
216    #[instrument(level = "debug", skip(self))]
217    fn visit_mod_contents(&mut self, def_id: LocalDefId, m: &'tcx hir::Mod<'tcx>) {
218        // Keep track of if there were any private modules in the path.
219        let orig_inside_public_path = self.inside_public_path;
220        self.inside_public_path &= self.cx.tcx.local_visibility(def_id).is_public();
221
222        // Reimplementation of `walk_mod` because we need to do it in two passes (explanations in
223        // the second loop):
224        let old_glob_mode = mem::replace(&mut self.glob_mode, GlobMode::NoGlob);
225        for &i in m.item_ids {
226            let item = self.cx.tcx.hir_item(i);
227            self.visit_item(item);
228        }
229        self.glob_mode = GlobMode::Only;
230        for &i in m.item_ids {
231            let item = self.cx.tcx.hir_item(i);
232            // To match the way import precedence works, visit glob imports last.
233            // Later passes in rustdoc will de-duplicate by name and kind, so if glob-
234            // imported items appear last, then they'll be the ones that get discarded.
235            if matches!(item.kind, hir::ItemKind::Use(..)) {
236                self.visit_item_inner(item, None, None);
237            }
238        }
239        self.glob_mode = old_glob_mode;
240        self.inside_public_path = orig_inside_public_path;
241    }
242
243    /// Tries to resolve the target of a `pub use` statement and inlines the
244    /// target if it is defined locally and would not be documented otherwise,
245    /// or when it is specifically requested with `please_inline`.
246    /// (the latter is the case when the import is marked `doc(inline)`)
247    ///
248    /// Cross-crate inlining occurs later on during crate cleaning
249    /// and follows different rules.
250    ///
251    /// Returns `true` if the target has been inlined.
252    #[instrument(level = "debug", skip(self), ret)]
253    fn maybe_inline_local(
254        &mut self,
255        def_id: LocalDefId,
256        res: Res,
257        renamed: Option<Symbol>,
258        please_inline: bool,
259        import_id: Option<LocalDefId>,
260    ) -> bool {
261        if renamed == Some(kw::Underscore) {
262            debug!("not inlining `_` reexports");
263            // We never inline `_` reexports.
264            return false;
265        }
266
267        if self.cx.is_json_output() {
268            return false;
269        }
270
271        let tcx = self.cx.tcx;
272        let Some(ori_res_did) = res.opt_def_id() else {
273            debug!("no resolution");
274            return false;
275        };
276
277        let document_hidden = self.cx.document_hidden();
278        let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
279        // Don't inline `doc(hidden)` imports so they can be stripped at a later stage.
280        let is_no_inline = find_attr!(
281            use_attrs,
282            Doc(d)
283            if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::NoInline)
284        ) || (document_hidden
285            && use_attrs.iter().any(|attr| attr.is_doc_hidden()));
286
287        if is_no_inline {
288            debug!("doc::no_inline or doc::hidden");
289            return false;
290        }
291
292        let is_glob = renamed.is_none();
293        let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
294        debug!(?is_hidden, ?is_glob);
295        let Some(res_did) = ori_res_did.as_local() else {
296            // For cross-crate impl inlining we need to know whether items are
297            // reachable in documentation -- a previously unreachable item can be
298            // made reachable by cross-crate inlining which we're checking here.
299            // (this is done here because we need to know this upfront).
300            crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
301            if is_hidden || is_glob {
302                return false;
303            }
304            // We store inlined foreign items otherwise, it'd mean that the `use` item would be kept
305            // around. It's not a problem unless this `use` imports both a local AND a foreign item.
306            // If a local item is inlined, its `use` is not supposed to still be around in `clean`,
307            // which would make appear the `use` in the generated documentation like the local item
308            // was not inlined even though it actually was.
309            self.modules
310                .last_mut()
311                .unwrap()
312                .inlined_foreigns
313                .insert((ori_res_did, renamed), InlinedForeign { res, import_id: def_id });
314            return true;
315        };
316
317        let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
318        debug!(?is_private);
319        let item = tcx.hir_node_by_def_id(res_did);
320
321        if !please_inline {
322            let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None);
323            debug!(?inherits_hidden);
324            // Only inline if requested or if the item would otherwise be stripped.
325            if (!is_private && !inherits_hidden) || (
326                is_hidden &&
327                // If it's a doc hidden module, we need to keep it in case some of its inner items
328                // are re-exported.
329                !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(..), .. }))
330            ) ||
331                // The imported item is public and not `doc(hidden)` so no need to inline it.
332                self.reexport_public_and_not_hidden(def_id, res_did)
333            {
334                return false;
335            }
336        }
337
338        let is_bang_macro = matches!(
339            item,
340            Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, _, kinds), .. }) if kinds.contains(MacroKinds::BANG)
341        );
342
343        if !self.view_item_stack.insert(res_did) && !is_bang_macro {
344            return false;
345        }
346
347        trace!(?item);
348
349        let inlined = match item {
350            // Bang macros are handled a bit on their because of how they are handled by the
351            // compiler. If they have `#[doc(hidden)]` and the re-export doesn't have
352            // `#[doc(inline)]`, then we don't inline it.
353            Node::NestedUseTree(..) | Node::Item(_)
354                if is_bang_macro && !please_inline && !is_glob && is_hidden =>
355            {
356                return false;
357            }
358            Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
359                let prev = mem::replace(&mut self.inlining, true);
360                let prev_glob = mem::replace(&mut self.glob_mode, GlobMode::Everything);
361                for &i in m.item_ids {
362                    let i = tcx.hir_item(i);
363                    self.visit_item_inner(i, None, Some(import_id.unwrap_or(def_id)));
364                }
365                self.glob_mode = prev_glob;
366                self.inlining = prev;
367                true
368            }
369            Node::Item(it) if !is_glob => {
370                debug!("inlining item");
371                let prev = mem::replace(&mut self.inlining, true);
372                self.visit_item_inner(it, renamed, Some(import_id.unwrap_or(def_id)));
373                self.inlining = prev;
374                true
375            }
376            Node::ForeignItem(it) if !is_glob => {
377                let prev = mem::replace(&mut self.inlining, true);
378                self.visit_foreign_item_inner(it, renamed, Some(import_id.unwrap_or(def_id)));
379                self.inlining = prev;
380                true
381            }
382            _ => false,
383        };
384        self.view_item_stack.remove(&res_did);
385        if inlined {
386            self.cx.cache.inlined_items.insert(ori_res_did);
387        }
388        inlined
389    }
390
391    /// Returns `true` if the item is visible, meaning it's not `#[doc(hidden)]` or private.
392    ///
393    /// This function takes into account the entire re-export `use` chain, so it needs the
394    /// ID of the "leaf" `use` and the ID of the "root" item.
395    #[instrument(level = "debug", skip(self), ret)]
396    fn reexport_public_and_not_hidden(
397        &self,
398        import_def_id: LocalDefId,
399        target_def_id: LocalDefId,
400    ) -> bool {
401        if self.cx.document_hidden() {
402            return true;
403        }
404        let tcx = self.cx.tcx;
405        let item_def_id = reexport_chain(tcx, import_def_id, target_def_id.to_def_id())
406            .iter()
407            .flat_map(|reexport| reexport.id())
408            .map(|id| id.expect_local())
409            .nth(1)
410            .unwrap_or(target_def_id);
411        debug!(?item_def_id);
412
413        item_def_id != import_def_id
414            && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
415            && !tcx.is_doc_hidden(item_def_id)
416            && !inherits_doc_hidden(tcx, item_def_id, None)
417    }
418
419    #[inline]
420    fn add_impl_to_current_mod(&mut self, item: &'tcx hir::Item<'_>, impl_: hir::Impl<'_>) {
421        self.add_to_current_mod(
422            item,
423            // The symbol here is used as a "sentinel" value and has no meaning in
424            // itself. It just tells that this is an inlined impl and that it should not
425            // be cleaned as a normal `ImplItem` but instead as a `PlaceholderImplItem`.
426            // It's to ensure that `doc_cfg` inheritance works as expected.
427            if impl_.of_trait.is_none() { None } else { Some(rustc_span::symbol::kw::Impl) },
428            None,
429        );
430    }
431
432    #[inline]
433    #[instrument(level = "debug", skip(self))]
434    fn add_to_current_mod(
435        &mut self,
436        item: &'tcx hir::Item<'_>,
437        mut renamed: Option<Symbol>,
438        import_id: Option<LocalDefId>,
439    ) {
440        if self.is_importable_from_parent
441            // If we're inside an item, only impl blocks and `macro_rules!` with the `macro_export`
442            // attribute can still be visible.
443            || match item.kind {
444                hir::ItemKind::Impl(..) => true,
445                hir::ItemKind::Macro(_, _, _) => {
446                    find_attr!(self.cx.tcx, item.owner_id.def_id, MacroExport{..})
447                }
448                _ => false,
449            }
450        {
451            if renamed == item.kind.ident().map(|ident| ident.name) {
452                renamed = None;
453            }
454            let key = (item.owner_id.def_id, renamed);
455            self.modules
456                .last_mut()
457                .unwrap()
458                .items
459                .entry(key)
460                .or_insert_with(|| ItemEntry { item, renamed, import_ids: vec![] })
461                .import_ids
462                .extend(import_id);
463        }
464    }
465
466    #[instrument(level = "debug", skip(self))]
467    fn visit_item_inner(
468        &mut self,
469        item: &'tcx hir::Item<'_>,
470        renamed: Option<Symbol>,
471        import_id: Option<LocalDefId>,
472    ) {
473        if self.inside_body {
474            // Only impls can be "seen" outside a body. For example:
475            //
476            // ```
477            // struct Bar;
478            //
479            // fn foo() {
480            //     impl Bar { fn bar() {} }
481            // }
482            // Bar::bar();
483            // ```
484            if let hir::ItemKind::Impl(impl_) = item.kind {
485                self.add_impl_to_current_mod(item, impl_);
486            }
487            return;
488        }
489        let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
490        let tcx = self.cx.tcx;
491
492        let def_id = item.owner_id.to_def_id();
493        let is_pub = tcx.visibility(def_id).is_public();
494
495        if is_pub {
496            self.store_path(item.owner_id.to_def_id());
497        }
498
499        match item.kind {
500            hir::ItemKind::ForeignMod { items, .. } => {
501                for &item in items {
502                    let item = tcx.hir_foreign_item(item);
503                    self.visit_foreign_item_inner(item, None, None);
504                }
505            }
506            // If we're inlining, skip private items.
507            _ if self.inlining && !is_pub => {}
508            hir::ItemKind::GlobalAsm { .. } => {}
509            hir::ItemKind::Use(ref tree) => {
510                self.visit_use_inner(
511                    item,
512                    renamed,
513                    import_id,
514                    is_pub,
515                    item.owner_id.def_id,
516                    item.hir_id(),
517                    tree,
518                );
519            }
520            hir::ItemKind::Macro(_, macro_def, _) => {
521                // `#[macro_export] macro_rules!` items are handled separately in `visit()`,
522                // above, since they need to be documented at the module top level. Accordingly,
523                // we only want to handle macros if one of three conditions holds:
524                //
525                // 1. This macro was defined by `macro`, and thus isn't covered by the case
526                //    above.
527                // 2. This macro isn't marked with `#[macro_export]`, and thus isn't covered
528                //    by the case above.
529                // 3. We're inlining, since a reexport where inlining has been requested
530                //    should be inlined even if it is also documented at the top level.
531
532                let def_id = item.owner_id.to_def_id();
533                let is_macro_2_0 = !macro_def.macro_rules;
534                let nonexported = !find_attr!(tcx, def_id, MacroExport { .. });
535
536                if is_macro_2_0 || nonexported || self.inlining {
537                    self.add_to_current_mod(item, renamed, import_id);
538                }
539            }
540            hir::ItemKind::Mod(_, m) => {
541                self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
542            }
543            hir::ItemKind::Fn { .. }
544            | hir::ItemKind::ExternCrate(..)
545            | hir::ItemKind::Enum(..)
546            | hir::ItemKind::Struct(..)
547            | hir::ItemKind::Union(..)
548            | hir::ItemKind::TyAlias(..)
549            | hir::ItemKind::Static(..)
550            | hir::ItemKind::Trait { .. }
551            | hir::ItemKind::TraitAlias(..) => {
552                self.add_to_current_mod(item, renamed, import_id);
553            }
554            hir::ItemKind::Const(..) => {
555                // Underscore constants do not correspond to a nameable item and
556                // so are never useful in documentation.
557                if get_name() != kw::Underscore {
558                    self.add_to_current_mod(item, renamed, import_id);
559                }
560            }
561            hir::ItemKind::Impl(impl_) => {
562                // Don't duplicate impls when inlining, we'll pick
563                // them up regardless of where they're located.
564                if !self.inlining {
565                    self.add_impl_to_current_mod(item, impl_);
566                }
567            }
568            hir::ItemKind::TestBinderConstraints { .. } => {}
569        }
570    }
571
572    #[instrument(level = "debug", skip(self))]
573    fn visit_use_inner(
574        &mut self,
575        item: &'tcx hir::Item<'tcx>,
576        renamed: Option<Symbol>,
577        import_id: Option<LocalDefId>,
578        is_pub: bool,
579        def_id: LocalDefId,
580        hir_id: HirId,
581        tree: &hir::UseTree<'tcx>,
582    ) {
583        let tcx = self.cx.tcx;
584        for res in tree.prefix.res.present_items() {
585            // Struct and variant constructors and proc macro stubs always show up alongside
586            // their definitions, we've already processed them so just discard these.
587            if should_ignore_res(res) {
588                continue;
589            }
590
591            let attrs = tcx.hir_attrs(hir_id);
592
593            // If there was a private module in the current path then don't bother inlining
594            // anything as it will probably be stripped anyway.
595            if is_pub && self.inside_public_path {
596                let please_inline = if let Some(res_did) = res.opt_def_id()
597                    && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG))
598                {
599                    crate::clean::macro_reexport_is_inline(tcx, def_id, res_did)
600                } else {
601                    find_attr!(
602                        attrs,
603                        Doc(d)
604                        if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline)
605                    )
606                };
607                let ident = match (tree.kind, self.glob_mode) {
608                    (hir::UseKind::Single(ident), GlobMode::NoGlob | GlobMode::Everything) => {
609                        Some(ident.name)
610                    }
611                    (hir::UseKind::Glob, GlobMode::Only | GlobMode::Everything) => None,
612                    (hir::UseKind::Single(_), GlobMode::Only)
613                    | (hir::UseKind::Glob, GlobMode::NoGlob) => continue,
614                    (hir::UseKind::Nested { items }, _) => {
615                        for (tree, hir_id, def_id) in items {
616                            self.visit_use_inner(
617                                item, renamed, import_id, is_pub, *def_id, *hir_id, tree,
618                            );
619                        }
620                        continue;
621                    }
622                };
623                if self.maybe_inline_local(def_id, res, ident, please_inline, import_id) {
624                    debug!("Inlining {:?}", def_id);
625                    continue;
626                }
627            }
628            self.add_to_current_mod(item, renamed, import_id);
629        }
630    }
631
632    fn visit_foreign_item_inner(
633        &mut self,
634        item: &'tcx hir::ForeignItem<'_>,
635        renamed: Option<Symbol>,
636        import_id: Option<LocalDefId>,
637    ) {
638        // If inlining we only want to include public functions.
639        if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
640            self.modules.last_mut().unwrap().foreigns.push(Foreign { item, renamed, import_id });
641        }
642    }
643
644    /// This method will create a new module and push it onto the "modules stack" then call
645    /// `visit_mod_contents`. Once done, it'll remove it from the "modules stack" and instead
646    /// add into the list of modules of the current module.
647    fn enter_mod(
648        &mut self,
649        id: LocalDefId,
650        m: &'tcx hir::Mod<'tcx>,
651        name: Symbol,
652        renamed: Option<Symbol>,
653        import_id: Option<LocalDefId>,
654    ) {
655        self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
656
657        self.visit_mod_contents(id, m);
658
659        let last = self.modules.pop().unwrap();
660        self.modules.last_mut().unwrap().mods.push(last);
661    }
662}
663
664// We need to implement this visitor so it'll go everywhere and retrieve items we're interested in
665// such as impl blocks in const blocks.
666impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
667    type NestedFilter = nested_filter::All;
668
669    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
670        self.cx.tcx
671    }
672
673    #[instrument(level = "debug", skip(self))]
674    fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
675        self.visit_item_inner(i, None, None);
676        let new_value = self.is_importable_from_parent
677            && matches!(
678                i.kind,
679                hir::ItemKind::Mod(..)
680                    | hir::ItemKind::ForeignMod { .. }
681                    | hir::ItemKind::Impl(..)
682                    | hir::ItemKind::Trait { .. }
683            );
684        let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
685        walk_item(self, i);
686        self.is_importable_from_parent = prev;
687    }
688
689    fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: LocalModId) {
690        // Handled in `visit_item_inner`
691    }
692
693    fn visit_use(
694        &mut self,
695        _: &hir::UseTree<'tcx>,
696        _: hir::HirId,
697        _: rustc_span::def_id::LocalDefId,
698    ) {
699        // Handled in `visit_item_inner`
700    }
701
702    fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
703        // Handled in `visit_item_inner`
704    }
705
706    fn visit_label(&mut self, _: &rustc_ast::Label) {
707        // Unneeded.
708    }
709
710    fn visit_infer(
711        &mut self,
712        _inf_id: hir::HirId,
713        _inf_span: Span,
714        _kind: hir::intravisit::InferKind<'tcx>,
715    ) -> Self::Result {
716        // Unneeded
717    }
718
719    fn visit_lifetime(&mut self, _: &hir::Lifetime) {
720        // Unneeded.
721    }
722
723    fn visit_body(&mut self, b: &hir::Body<'tcx>) {
724        let prev = mem::replace(&mut self.inside_body, true);
725        walk_body(self, b);
726        self.inside_body = prev;
727    }
728}