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