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    ) -> bool {
248        debug!("maybe_inline_local (renamed: {renamed:?}) res: {res:?}");
249
250        if renamed == Some(kw::Underscore) {
251            // We never inline `_` reexports.
252            return false;
253        }
254
255        if self.cx.is_json_output() {
256            return false;
257        }
258
259        let tcx = self.cx.tcx;
260        let Some(ori_res_did) = res.opt_def_id() else {
261            return false;
262        };
263
264        let document_hidden = self.cx.document_hidden();
265        let use_attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
266        // Don't inline `doc(hidden)` imports so they can be stripped at a later stage.
267        let is_no_inline = find_attr!(
268            use_attrs,
269            Doc(d)
270            if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::NoInline)
271        ) || (document_hidden
272            && use_attrs.iter().any(|attr| attr.is_doc_hidden()));
273
274        if is_no_inline {
275            return false;
276        }
277
278        let is_glob = renamed.is_none();
279        let is_hidden = !document_hidden && tcx.is_doc_hidden(ori_res_did);
280        let Some(res_did) = ori_res_did.as_local() else {
281            // For cross-crate impl inlining we need to know whether items are
282            // reachable in documentation -- a previously unreachable item can be
283            // made reachable by cross-crate inlining which we're checking here.
284            // (this is done here because we need to know this upfront).
285            crate::visit_lib::lib_embargo_visit_item(self.cx, ori_res_did);
286            if is_hidden || is_glob {
287                return false;
288            }
289            // We store inlined foreign items otherwise, it'd mean that the `use` item would be kept
290            // around. It's not a problem unless this `use` imports both a local AND a foreign item.
291            // If a local item is inlined, its `use` is not supposed to still be around in `clean`,
292            // which would make appear the `use` in the generated documentation like the local item
293            // was not inlined even though it actually was.
294            self.modules
295                .last_mut()
296                .unwrap()
297                .inlined_foreigns
298                .insert((ori_res_did, renamed), InlinedForeign { res, import_id: def_id });
299            return true;
300        };
301
302        let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did);
303        let item = tcx.hir_node_by_def_id(res_did);
304
305        if !please_inline {
306            let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None);
307            // Only inline if requested or if the item would otherwise be stripped.
308            if (!is_private && !inherits_hidden) || (
309                is_hidden &&
310                // If it's a doc hidden module, we need to keep it in case some of its inner items
311                // are re-exported.
312                !matches!(item, Node::Item(&hir::Item { kind: hir::ItemKind::Mod(..), .. }))
313            ) ||
314                // The imported item is public and not `doc(hidden)` so no need to inline it.
315                self.reexport_public_and_not_hidden(def_id, res_did)
316            {
317                return false;
318            }
319        }
320
321        let is_bang_macro = matches!(
322            item,
323            Node::Item(&hir::Item { kind: hir::ItemKind::Macro(_, _, kinds), .. }) if kinds.contains(MacroKinds::BANG)
324        );
325
326        if !self.view_item_stack.insert(res_did) && !is_bang_macro {
327            return false;
328        }
329
330        let inlined = match item {
331            // Bang macros are handled a bit on their because of how they are handled by the
332            // compiler. If they have `#[doc(hidden)]` and the re-export doesn't have
333            // `#[doc(inline)]`, then we don't inline it.
334            Node::Item(_) if is_bang_macro && !please_inline && !is_glob && is_hidden => {
335                return false;
336            }
337            Node::Item(&hir::Item { kind: hir::ItemKind::Mod(_, m), .. }) if is_glob => {
338                let prev = mem::replace(&mut self.inlining, true);
339                for &i in m.item_ids {
340                    let i = tcx.hir_item(i);
341                    self.visit_item_inner(i, None, Some(def_id));
342                }
343                self.inlining = prev;
344                true
345            }
346            Node::Item(it) if !is_glob => {
347                let prev = mem::replace(&mut self.inlining, true);
348                self.visit_item_inner(it, renamed, Some(def_id));
349                self.inlining = prev;
350                true
351            }
352            Node::ForeignItem(it) if !is_glob => {
353                let prev = mem::replace(&mut self.inlining, true);
354                self.visit_foreign_item_inner(it, renamed, Some(def_id));
355                self.inlining = prev;
356                true
357            }
358            _ => false,
359        };
360        self.view_item_stack.remove(&res_did);
361        if inlined {
362            self.cx.cache.inlined_items.insert(ori_res_did);
363        }
364        inlined
365    }
366
367    /// Returns `true` if the item is visible, meaning it's not `#[doc(hidden)]` or private.
368    ///
369    /// This function takes into account the entire re-export `use` chain, so it needs the
370    /// ID of the "leaf" `use` and the ID of the "root" item.
371    fn reexport_public_and_not_hidden(
372        &self,
373        import_def_id: LocalDefId,
374        target_def_id: LocalDefId,
375    ) -> bool {
376        if self.cx.document_hidden() {
377            return true;
378        }
379        let tcx = self.cx.tcx;
380        let item_def_id = reexport_chain(tcx, import_def_id, target_def_id.to_def_id())
381            .iter()
382            .flat_map(|reexport| reexport.id())
383            .map(|id| id.expect_local())
384            .nth(1)
385            .unwrap_or(target_def_id);
386        item_def_id != import_def_id
387            && self.cx.cache.effective_visibilities.is_directly_public(tcx, item_def_id.to_def_id())
388            && !tcx.is_doc_hidden(item_def_id)
389            && !inherits_doc_hidden(tcx, item_def_id, None)
390    }
391
392    #[inline]
393    fn add_impl_to_current_mod(&mut self, item: &'tcx hir::Item<'_>, impl_: hir::Impl<'_>) {
394        self.add_to_current_mod(
395            item,
396            // The symbol here is used as a "sentinel" value and has no meaning in
397            // itself. It just tells that this is an inlined impl and that it should not
398            // be cleaned as a normal `ImplItem` but instead as a `PlaceholderImplItem`.
399            // It's to ensure that `doc_cfg` inheritance works as expected.
400            if impl_.of_trait.is_none() { None } else { Some(rustc_span::symbol::kw::Impl) },
401            None,
402        );
403    }
404
405    #[inline]
406    fn add_to_current_mod(
407        &mut self,
408        item: &'tcx hir::Item<'_>,
409        mut renamed: Option<Symbol>,
410        import_id: Option<LocalDefId>,
411    ) {
412        if self.is_importable_from_parent
413            // If we're inside an item, only impl blocks and `macro_rules!` with the `macro_export`
414            // attribute can still be visible.
415            || match item.kind {
416                hir::ItemKind::Impl(..) => true,
417                hir::ItemKind::Macro(_, _, _) => {
418                    find_attr!(self.cx.tcx, item.owner_id.def_id, MacroExport{..})
419                }
420                _ => false,
421            }
422        {
423            if renamed == item.kind.ident().map(|ident| ident.name) {
424                renamed = None;
425            }
426            let key = (item.owner_id.def_id, renamed);
427            if let Some(import_id) = import_id {
428                self.modules
429                    .last_mut()
430                    .unwrap()
431                    .items
432                    .entry(key)
433                    .and_modify(|v| v.import_ids.push(import_id))
434                    .or_insert_with(|| ItemEntry { item, renamed, import_ids: vec![import_id] });
435            } else {
436                self.modules
437                    .last_mut()
438                    .unwrap()
439                    .items
440                    .insert(key, ItemEntry { item, renamed, import_ids: Vec::new() });
441            }
442        }
443    }
444
445    fn visit_item_inner(
446        &mut self,
447        item: &'tcx hir::Item<'_>,
448        renamed: Option<Symbol>,
449        import_id: Option<LocalDefId>,
450    ) {
451        debug!("visiting item {item:?}");
452        if self.inside_body {
453            // Only impls can be "seen" outside a body. For example:
454            //
455            // ```
456            // struct Bar;
457            //
458            // fn foo() {
459            //     impl Bar { fn bar() {} }
460            // }
461            // Bar::bar();
462            // ```
463            if let hir::ItemKind::Impl(impl_) = item.kind {
464                self.add_impl_to_current_mod(item, impl_);
465            }
466            return;
467        }
468        let get_name = || renamed.unwrap_or(item.kind.ident().unwrap().name);
469        let tcx = self.cx.tcx;
470
471        let def_id = item.owner_id.to_def_id();
472        let is_pub = tcx.visibility(def_id).is_public();
473
474        if is_pub {
475            self.store_path(item.owner_id.to_def_id());
476        }
477
478        match item.kind {
479            hir::ItemKind::ForeignMod { items, .. } => {
480                for &item in items {
481                    let item = tcx.hir_foreign_item(item);
482                    self.visit_foreign_item_inner(item, None, None);
483                }
484            }
485            // If we're inlining, skip private items.
486            _ if self.inlining && !is_pub => {}
487            hir::ItemKind::GlobalAsm { .. } => {}
488            hir::ItemKind::Use(_, hir::UseKind::ListStem) => {}
489            hir::ItemKind::Use(path, kind) => {
490                for res in path.res.present_items() {
491                    // Struct and variant constructors and proc macro stubs always show up alongside
492                    // their definitions, we've already processed them so just discard these.
493                    if should_ignore_res(res) {
494                        continue;
495                    }
496
497                    let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(item.owner_id.def_id));
498
499                    // If there was a private module in the current path then don't bother inlining
500                    // anything as it will probably be stripped anyway.
501                    if is_pub && self.inside_public_path {
502                        let please_inline = if let Some(res_did) = res.opt_def_id()
503                            && matches!(tcx.def_kind(res_did), DefKind::Macro(MacroKinds::BANG))
504                        {
505                            crate::clean::macro_reexport_is_inline(
506                                tcx,
507                                item.owner_id.def_id,
508                                res_did,
509                            )
510                        } else {
511                            find_attr!(
512                                attrs,
513                                Doc(d)
514                                if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline)
515                            )
516                        };
517                        let ident = match kind {
518                            hir::UseKind::Single(ident) => Some(ident.name),
519                            hir::UseKind::Glob => None,
520                            hir::UseKind::ListStem => unreachable!(),
521                        };
522                        if self.maybe_inline_local(item.owner_id.def_id, res, ident, please_inline)
523                        {
524                            debug!("Inlining {:?}", item.owner_id.def_id);
525                            continue;
526                        }
527                    }
528                    self.add_to_current_mod(item, renamed, import_id);
529                }
530            }
531            hir::ItemKind::Macro(_, macro_def, _) => {
532                // `#[macro_export] macro_rules!` items are handled separately in `visit()`,
533                // above, since they need to be documented at the module top level. Accordingly,
534                // we only want to handle macros if one of three conditions holds:
535                //
536                // 1. This macro was defined by `macro`, and thus isn't covered by the case
537                //    above.
538                // 2. This macro isn't marked with `#[macro_export]`, and thus isn't covered
539                //    by the case above.
540                // 3. We're inlining, since a reexport where inlining has been requested
541                //    should be inlined even if it is also documented at the top level.
542
543                let def_id = item.owner_id.to_def_id();
544                let is_macro_2_0 = !macro_def.macro_rules;
545                let nonexported = !find_attr!(tcx, def_id, MacroExport { .. });
546
547                if is_macro_2_0 || nonexported || self.inlining {
548                    self.add_to_current_mod(item, renamed, import_id);
549                }
550            }
551            hir::ItemKind::Mod(_, m) => {
552                self.enter_mod(item.owner_id.def_id, m, get_name(), renamed, import_id);
553            }
554            hir::ItemKind::Fn { .. }
555            | hir::ItemKind::ExternCrate(..)
556            | hir::ItemKind::Enum(..)
557            | hir::ItemKind::Struct(..)
558            | hir::ItemKind::Union(..)
559            | hir::ItemKind::TyAlias(..)
560            | hir::ItemKind::Static(..)
561            | hir::ItemKind::Trait { .. }
562            | hir::ItemKind::TraitAlias(..) => {
563                self.add_to_current_mod(item, renamed, import_id);
564            }
565            hir::ItemKind::Const(..) => {
566                // Underscore constants do not correspond to a nameable item and
567                // so are never useful in documentation.
568                if get_name() != kw::Underscore {
569                    self.add_to_current_mod(item, renamed, import_id);
570                }
571            }
572            hir::ItemKind::Impl(impl_) => {
573                // Don't duplicate impls when inlining, we'll pick
574                // them up regardless of where they're located.
575                if !self.inlining {
576                    self.add_impl_to_current_mod(item, impl_);
577                }
578            }
579        }
580    }
581
582    fn visit_foreign_item_inner(
583        &mut self,
584        item: &'tcx hir::ForeignItem<'_>,
585        renamed: Option<Symbol>,
586        import_id: Option<LocalDefId>,
587    ) {
588        // If inlining we only want to include public functions.
589        if !self.inlining || self.cx.tcx.visibility(item.owner_id).is_public() {
590            self.modules.last_mut().unwrap().foreigns.push(Foreign { item, renamed, import_id });
591        }
592    }
593
594    /// This method will create a new module and push it onto the "modules stack" then call
595    /// `visit_mod_contents`. Once done, it'll remove it from the "modules stack" and instead
596    /// add into the list of modules of the current module.
597    fn enter_mod(
598        &mut self,
599        id: LocalDefId,
600        m: &'tcx hir::Mod<'tcx>,
601        name: Symbol,
602        renamed: Option<Symbol>,
603        import_id: Option<LocalDefId>,
604    ) {
605        self.modules.push(Module::new(name, id, m.spans.inner_span, renamed, import_id));
606
607        self.visit_mod_contents(id, m);
608
609        let last = self.modules.pop().unwrap();
610        self.modules.last_mut().unwrap().mods.push(last);
611    }
612}
613
614// We need to implement this visitor so it'll go everywhere and retrieve items we're interested in
615// such as impl blocks in const blocks.
616impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> {
617    type NestedFilter = nested_filter::All;
618
619    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
620        self.cx.tcx
621    }
622
623    fn visit_item(&mut self, i: &'tcx hir::Item<'tcx>) {
624        self.visit_item_inner(i, None, None);
625        let new_value = self.is_importable_from_parent
626            && matches!(
627                i.kind,
628                hir::ItemKind::Mod(..)
629                    | hir::ItemKind::ForeignMod { .. }
630                    | hir::ItemKind::Impl(..)
631                    | hir::ItemKind::Trait { .. }
632            );
633        let prev = mem::replace(&mut self.is_importable_from_parent, new_value);
634        walk_item(self, i);
635        self.is_importable_from_parent = prev;
636    }
637
638    fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) {
639        // Handled in `visit_item_inner`
640    }
641
642    fn visit_use(&mut self, _: &hir::UsePath<'tcx>, _: hir::HirId) {
643        // Handled in `visit_item_inner`
644    }
645
646    fn visit_path(&mut self, _: &hir::Path<'tcx>, _: hir::HirId) {
647        // Handled in `visit_item_inner`
648    }
649
650    fn visit_label(&mut self, _: &rustc_ast::Label) {
651        // Unneeded.
652    }
653
654    fn visit_infer(
655        &mut self,
656        _inf_id: hir::HirId,
657        _inf_span: Span,
658        _kind: hir::intravisit::InferKind<'tcx>,
659    ) -> Self::Result {
660        // Unneeded
661    }
662
663    fn visit_lifetime(&mut self, _: &hir::Lifetime) {
664        // Unneeded.
665    }
666
667    fn visit_body(&mut self, b: &hir::Body<'tcx>) {
668        let prev = mem::replace(&mut self.inside_body, true);
669        walk_body(self, b);
670        self.inside_body = prev;
671    }
672}