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