rustc_passes/
input_stats.rs

1// The visitors in this module collect sizes and counts of the most important
2// pieces of AST and HIR. The resulting numbers are good approximations but not
3// completely accurate (some things might be counted twice, others missed).
4
5use rustc_ast::visit::BoundKind;
6use rustc_ast::{self as ast, NodeId, visit as ast_visit};
7use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8use rustc_data_structures::thousands::format_with_underscores;
9use rustc_hir::{self as hir, AmbigArg, HirId, intravisit as hir_visit};
10use rustc_middle::ty::TyCtxt;
11use rustc_span::Span;
12use rustc_span::def_id::LocalDefId;
13
14struct NodeStats {
15    count: usize,
16    size: usize,
17}
18
19impl NodeStats {
20    fn new() -> NodeStats {
21        NodeStats { count: 0, size: 0 }
22    }
23
24    fn accum_size(&self) -> usize {
25        self.count * self.size
26    }
27}
28
29struct Node {
30    stats: NodeStats,
31    subnodes: FxHashMap<&'static str, NodeStats>,
32}
33
34impl Node {
35    fn new() -> Node {
36        Node { stats: NodeStats::new(), subnodes: FxHashMap::default() }
37    }
38}
39
40/// This type measures the size of AST and HIR nodes, by implementing the AST
41/// and HIR `Visitor` traits. But we don't measure every visited type because
42/// that could cause double counting.
43///
44/// For example, `ast::Visitor` has `visit_ident`, but `Ident`s are always
45/// stored inline within other AST nodes, so we don't implement `visit_ident`
46/// here. In contrast, we do implement `visit_expr` because `ast::Expr` is
47/// always stored as `P<ast::Expr>`, and every such expression should be
48/// measured separately.
49///
50/// In general, a `visit_foo` method should be implemented here if the
51/// corresponding `Foo` type is always stored on its own, e.g.: `P<Foo>`,
52/// `Box<Foo>`, `Vec<Foo>`, `Box<[Foo]>`.
53///
54/// There are some types in the AST and HIR tree that the visitors do not have
55/// a `visit_*` method for, and so we cannot measure these, which is
56/// unfortunate.
57struct StatCollector<'k> {
58    tcx: Option<TyCtxt<'k>>,
59    nodes: FxHashMap<&'static str, Node>,
60    seen: FxHashSet<HirId>,
61}
62
63pub fn print_hir_stats(tcx: TyCtxt<'_>) {
64    let mut collector =
65        StatCollector { tcx: Some(tcx), nodes: FxHashMap::default(), seen: FxHashSet::default() };
66    tcx.hir_walk_toplevel_module(&mut collector);
67    tcx.hir_walk_attributes(&mut collector);
68    collector.print("HIR STATS", "hir-stats");
69}
70
71pub fn print_ast_stats(krate: &ast::Crate, title: &str, prefix: &str) {
72    use rustc_ast::visit::Visitor;
73
74    let mut collector =
75        StatCollector { tcx: None, nodes: FxHashMap::default(), seen: FxHashSet::default() };
76    collector.visit_crate(krate);
77    collector.print(title, prefix);
78}
79
80impl<'k> StatCollector<'k> {
81    // Record a top-level node.
82    fn record<T>(&mut self, label: &'static str, id: Option<HirId>, val: &T) {
83        self.record_inner(label, None, id, val);
84    }
85
86    // Record a two-level entry, with a top-level enum type and a variant.
87    fn record_variant<T>(
88        &mut self,
89        label1: &'static str,
90        label2: &'static str,
91        id: Option<HirId>,
92        val: &T,
93    ) {
94        self.record_inner(label1, Some(label2), id, val);
95    }
96
97    fn record_inner<T>(
98        &mut self,
99        label1: &'static str,
100        label2: Option<&'static str>,
101        id: Option<HirId>,
102        val: &T,
103    ) {
104        if id.is_some_and(|x| !self.seen.insert(x)) {
105            return;
106        }
107
108        let node = self.nodes.entry(label1).or_insert(Node::new());
109        node.stats.count += 1;
110        node.stats.size = size_of_val(val);
111
112        if let Some(label2) = label2 {
113            let subnode = node.subnodes.entry(label2).or_insert(NodeStats::new());
114            subnode.count += 1;
115            subnode.size = size_of_val(val);
116        }
117    }
118
119    fn print(&self, title: &str, prefix: &str) {
120        // We will soon sort, so the initial order does not matter.
121        #[allow(rustc::potential_query_instability)]
122        let mut nodes: Vec<_> = self.nodes.iter().collect();
123        nodes.sort_by_cached_key(|(label, node)| (node.stats.accum_size(), label.to_owned()));
124
125        let total_size = nodes.iter().map(|(_, node)| node.stats.accum_size()).sum();
126        let total_count = nodes.iter().map(|(_, node)| node.stats.count).sum();
127
128        eprintln!("{prefix} {title}");
129        eprintln!(
130            "{} {:<18}{:>18}{:>14}{:>14}",
131            prefix, "Name", "Accumulated Size", "Count", "Item Size"
132        );
133        eprintln!("{prefix} ----------------------------------------------------------------");
134
135        let percent = |m, n| (m * 100) as f64 / n as f64;
136
137        for (label, node) in nodes {
138            let size = node.stats.accum_size();
139            eprintln!(
140                "{} {:<18}{:>10} ({:4.1}%){:>14}{:>14}",
141                prefix,
142                label,
143                format_with_underscores(size),
144                percent(size, total_size),
145                format_with_underscores(node.stats.count),
146                format_with_underscores(node.stats.size)
147            );
148            if !node.subnodes.is_empty() {
149                // We will soon sort, so the initial order does not matter.
150                #[allow(rustc::potential_query_instability)]
151                let mut subnodes: Vec<_> = node.subnodes.iter().collect();
152                subnodes.sort_by_cached_key(|(label, subnode)| {
153                    (subnode.accum_size(), label.to_owned())
154                });
155
156                for (label, subnode) in subnodes {
157                    let size = subnode.accum_size();
158                    eprintln!(
159                        "{} - {:<18}{:>10} ({:4.1}%){:>14}",
160                        prefix,
161                        label,
162                        format_with_underscores(size),
163                        percent(size, total_size),
164                        format_with_underscores(subnode.count),
165                    );
166                }
167            }
168        }
169        eprintln!("{prefix} ----------------------------------------------------------------");
170        eprintln!(
171            "{} {:<18}{:>10}        {:>14}",
172            prefix,
173            "Total",
174            format_with_underscores(total_size),
175            format_with_underscores(total_count),
176        );
177        eprintln!("{prefix}");
178    }
179}
180
181// Used to avoid boilerplate for types with many variants.
182macro_rules! record_variants {
183    (
184        ($self:ident, $val:expr, $kind:expr, $id:expr, $mod:ident, $ty:ty, $tykind:ident),
185        [$($variant:ident),*]
186    ) => {
187        match $kind {
188            $(
189                $mod::$tykind::$variant { .. } => {
190                    $self.record_variant(stringify!($ty), stringify!($variant), $id, $val)
191                }
192            )*
193        }
194    };
195}
196
197impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
198    fn visit_param(&mut self, param: &'v hir::Param<'v>) {
199        self.record("Param", Some(param.hir_id), param);
200        hir_visit::walk_param(self, param)
201    }
202
203    fn visit_nested_item(&mut self, id: hir::ItemId) {
204        let nested_item = self.tcx.unwrap().hir_item(id);
205        self.visit_item(nested_item)
206    }
207
208    fn visit_nested_trait_item(&mut self, trait_item_id: hir::TraitItemId) {
209        let nested_trait_item = self.tcx.unwrap().hir_trait_item(trait_item_id);
210        self.visit_trait_item(nested_trait_item)
211    }
212
213    fn visit_nested_impl_item(&mut self, impl_item_id: hir::ImplItemId) {
214        let nested_impl_item = self.tcx.unwrap().hir_impl_item(impl_item_id);
215        self.visit_impl_item(nested_impl_item)
216    }
217
218    fn visit_nested_foreign_item(&mut self, id: hir::ForeignItemId) {
219        let nested_foreign_item = self.tcx.unwrap().hir_foreign_item(id);
220        self.visit_foreign_item(nested_foreign_item);
221    }
222
223    fn visit_nested_body(&mut self, body_id: hir::BodyId) {
224        let nested_body = self.tcx.unwrap().hir_body(body_id);
225        self.visit_body(nested_body)
226    }
227
228    fn visit_item(&mut self, i: &'v hir::Item<'v>) {
229        record_variants!(
230            (self, i, i.kind, Some(i.hir_id()), hir, Item, ItemKind),
231            [
232                ExternCrate,
233                Use,
234                Static,
235                Const,
236                Fn,
237                Macro,
238                Mod,
239                ForeignMod,
240                GlobalAsm,
241                TyAlias,
242                Enum,
243                Struct,
244                Union,
245                Trait,
246                TraitAlias,
247                Impl
248            ]
249        );
250        hir_visit::walk_item(self, i)
251    }
252
253    fn visit_body(&mut self, b: &hir::Body<'v>) {
254        self.record("Body", None, b);
255        hir_visit::walk_body(self, b);
256    }
257
258    fn visit_mod(&mut self, m: &'v hir::Mod<'v>, _s: Span, _n: HirId) {
259        self.record("Mod", None, m);
260        hir_visit::walk_mod(self, m)
261    }
262
263    fn visit_foreign_item(&mut self, i: &'v hir::ForeignItem<'v>) {
264        record_variants!(
265            (self, i, i.kind, Some(i.hir_id()), hir, ForeignItem, ForeignItemKind),
266            [Fn, Static, Type]
267        );
268        hir_visit::walk_foreign_item(self, i)
269    }
270
271    fn visit_local(&mut self, l: &'v hir::LetStmt<'v>) {
272        self.record("Local", Some(l.hir_id), l);
273        hir_visit::walk_local(self, l)
274    }
275
276    fn visit_block(&mut self, b: &'v hir::Block<'v>) {
277        self.record("Block", Some(b.hir_id), b);
278        hir_visit::walk_block(self, b)
279    }
280
281    fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) {
282        record_variants!(
283            (self, s, s.kind, Some(s.hir_id), hir, Stmt, StmtKind),
284            [Let, Item, Expr, Semi]
285        );
286        hir_visit::walk_stmt(self, s)
287    }
288
289    fn visit_arm(&mut self, a: &'v hir::Arm<'v>) {
290        self.record("Arm", Some(a.hir_id), a);
291        hir_visit::walk_arm(self, a)
292    }
293
294    fn visit_pat(&mut self, p: &'v hir::Pat<'v>) {
295        record_variants!(
296            (self, p, p.kind, Some(p.hir_id), hir, Pat, PatKind),
297            [
298                Wild,
299                Binding,
300                Struct,
301                TupleStruct,
302                Or,
303                Never,
304                Tuple,
305                Box,
306                Deref,
307                Ref,
308                Expr,
309                Guard,
310                Range,
311                Slice,
312                Err
313            ]
314        );
315        hir_visit::walk_pat(self, p)
316    }
317
318    fn visit_pat_field(&mut self, f: &'v hir::PatField<'v>) {
319        self.record("PatField", Some(f.hir_id), f);
320        hir_visit::walk_pat_field(self, f)
321    }
322
323    fn visit_expr(&mut self, e: &'v hir::Expr<'v>) {
324        record_variants!(
325            (self, e, e.kind, Some(e.hir_id), hir, Expr, ExprKind),
326            [
327                ConstBlock,
328                Array,
329                Call,
330                MethodCall,
331                Use,
332                Tup,
333                Binary,
334                Unary,
335                Lit,
336                Cast,
337                Type,
338                DropTemps,
339                Let,
340                If,
341                Loop,
342                Match,
343                Closure,
344                Block,
345                Assign,
346                AssignOp,
347                Field,
348                Index,
349                Path,
350                AddrOf,
351                Break,
352                Continue,
353                Ret,
354                Become,
355                InlineAsm,
356                OffsetOf,
357                Struct,
358                Repeat,
359                Yield,
360                UnsafeBinderCast,
361                Err
362            ]
363        );
364        hir_visit::walk_expr(self, e)
365    }
366
367    fn visit_expr_field(&mut self, f: &'v hir::ExprField<'v>) {
368        self.record("ExprField", Some(f.hir_id), f);
369        hir_visit::walk_expr_field(self, f)
370    }
371
372    fn visit_ty(&mut self, t: &'v hir::Ty<'v, AmbigArg>) {
373        record_variants!(
374            (self, t, t.kind, Some(t.hir_id), hir, Ty, TyKind),
375            [
376                InferDelegation,
377                Slice,
378                Array,
379                Ptr,
380                Ref,
381                BareFn,
382                UnsafeBinder,
383                Never,
384                Tup,
385                Path,
386                OpaqueDef,
387                TraitAscription,
388                TraitObject,
389                Typeof,
390                Infer,
391                Pat,
392                Err
393            ]
394        );
395        hir_visit::walk_ty(self, t)
396    }
397
398    fn visit_generic_param(&mut self, p: &'v hir::GenericParam<'v>) {
399        self.record("GenericParam", Some(p.hir_id), p);
400        hir_visit::walk_generic_param(self, p)
401    }
402
403    fn visit_generics(&mut self, g: &'v hir::Generics<'v>) {
404        self.record("Generics", None, g);
405        hir_visit::walk_generics(self, g)
406    }
407
408    fn visit_where_predicate(&mut self, p: &'v hir::WherePredicate<'v>) {
409        record_variants!(
410            (self, p, p.kind, Some(p.hir_id), hir, WherePredicate, WherePredicateKind),
411            [BoundPredicate, RegionPredicate, EqPredicate]
412        );
413        hir_visit::walk_where_predicate(self, p)
414    }
415
416    fn visit_fn(
417        &mut self,
418        fk: hir_visit::FnKind<'v>,
419        fd: &'v hir::FnDecl<'v>,
420        b: hir::BodyId,
421        _: Span,
422        id: LocalDefId,
423    ) {
424        self.record("FnDecl", None, fd);
425        hir_visit::walk_fn(self, fk, fd, b, id)
426    }
427
428    fn visit_use(&mut self, p: &'v hir::UsePath<'v>, hir_id: HirId) {
429        // This is `visit_use`, but the type is `Path` so record it that way.
430        self.record("Path", None, p);
431        hir_visit::walk_use(self, p, hir_id)
432    }
433
434    fn visit_trait_item(&mut self, ti: &'v hir::TraitItem<'v>) {
435        record_variants!(
436            (self, ti, ti.kind, Some(ti.hir_id()), hir, TraitItem, TraitItemKind),
437            [Const, Fn, Type]
438        );
439        hir_visit::walk_trait_item(self, ti)
440    }
441
442    fn visit_trait_item_ref(&mut self, ti: &'v hir::TraitItemRef) {
443        self.record("TraitItemRef", Some(ti.id.hir_id()), ti);
444        hir_visit::walk_trait_item_ref(self, ti)
445    }
446
447    fn visit_impl_item(&mut self, ii: &'v hir::ImplItem<'v>) {
448        record_variants!(
449            (self, ii, ii.kind, Some(ii.hir_id()), hir, ImplItem, ImplItemKind),
450            [Const, Fn, Type]
451        );
452        hir_visit::walk_impl_item(self, ii)
453    }
454
455    fn visit_foreign_item_ref(&mut self, fi: &'v hir::ForeignItemRef) {
456        self.record("ForeignItemRef", Some(fi.id.hir_id()), fi);
457        hir_visit::walk_foreign_item_ref(self, fi)
458    }
459
460    fn visit_impl_item_ref(&mut self, ii: &'v hir::ImplItemRef) {
461        self.record("ImplItemRef", Some(ii.id.hir_id()), ii);
462        hir_visit::walk_impl_item_ref(self, ii)
463    }
464
465    fn visit_param_bound(&mut self, b: &'v hir::GenericBound<'v>) {
466        record_variants!(
467            (self, b, b, None, hir, GenericBound, GenericBound),
468            [Trait, Outlives, Use]
469        );
470        hir_visit::walk_param_bound(self, b)
471    }
472
473    fn visit_field_def(&mut self, s: &'v hir::FieldDef<'v>) {
474        self.record("FieldDef", Some(s.hir_id), s);
475        hir_visit::walk_field_def(self, s)
476    }
477
478    fn visit_variant(&mut self, v: &'v hir::Variant<'v>) {
479        self.record("Variant", None, v);
480        hir_visit::walk_variant(self, v)
481    }
482
483    fn visit_generic_arg(&mut self, ga: &'v hir::GenericArg<'v>) {
484        record_variants!(
485            (self, ga, ga, Some(ga.hir_id()), hir, GenericArg, GenericArg),
486            [Lifetime, Type, Const, Infer]
487        );
488        match ga {
489            hir::GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
490            hir::GenericArg::Type(ty) => self.visit_ty(ty),
491            hir::GenericArg::Const(ct) => self.visit_const_arg(ct),
492            hir::GenericArg::Infer(inf) => self.visit_id(inf.hir_id),
493        }
494    }
495
496    fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
497        self.record("Lifetime", Some(lifetime.hir_id), lifetime);
498        hir_visit::walk_lifetime(self, lifetime)
499    }
500
501    fn visit_path(&mut self, path: &hir::Path<'v>, _id: HirId) {
502        self.record("Path", None, path);
503        hir_visit::walk_path(self, path)
504    }
505
506    fn visit_path_segment(&mut self, path_segment: &'v hir::PathSegment<'v>) {
507        self.record("PathSegment", None, path_segment);
508        hir_visit::walk_path_segment(self, path_segment)
509    }
510
511    fn visit_generic_args(&mut self, ga: &'v hir::GenericArgs<'v>) {
512        self.record("GenericArgs", None, ga);
513        hir_visit::walk_generic_args(self, ga)
514    }
515
516    fn visit_assoc_item_constraint(&mut self, constraint: &'v hir::AssocItemConstraint<'v>) {
517        self.record("AssocItemConstraint", Some(constraint.hir_id), constraint);
518        hir_visit::walk_assoc_item_constraint(self, constraint)
519    }
520
521    fn visit_attribute(&mut self, attr: &'v hir::Attribute) {
522        self.record("Attribute", None, attr);
523    }
524
525    fn visit_inline_asm(&mut self, asm: &'v hir::InlineAsm<'v>, id: HirId) {
526        self.record("InlineAsm", None, asm);
527        hir_visit::walk_inline_asm(self, asm, id);
528    }
529}
530
531impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
532    fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
533        record_variants!(
534            (self, i, i.kind, None, ast, ForeignItem, ForeignItemKind),
535            [Static, Fn, TyAlias, MacCall]
536        );
537        ast_visit::walk_item(self, i)
538    }
539
540    fn visit_item(&mut self, i: &'v ast::Item) {
541        record_variants!(
542            (self, i, i.kind, None, ast, Item, ItemKind),
543            [
544                ExternCrate,
545                Use,
546                Static,
547                Const,
548                Fn,
549                Mod,
550                ForeignMod,
551                GlobalAsm,
552                TyAlias,
553                Enum,
554                Struct,
555                Union,
556                Trait,
557                TraitAlias,
558                Impl,
559                MacCall,
560                MacroDef,
561                Delegation,
562                DelegationMac
563            ]
564        );
565        ast_visit::walk_item(self, i)
566    }
567
568    fn visit_local(&mut self, l: &'v ast::Local) {
569        self.record("Local", None, l);
570        ast_visit::walk_local(self, l)
571    }
572
573    fn visit_block(&mut self, b: &'v ast::Block) {
574        self.record("Block", None, b);
575        ast_visit::walk_block(self, b)
576    }
577
578    fn visit_stmt(&mut self, s: &'v ast::Stmt) {
579        record_variants!(
580            (self, s, s.kind, None, ast, Stmt, StmtKind),
581            [Let, Item, Expr, Semi, Empty, MacCall]
582        );
583        ast_visit::walk_stmt(self, s)
584    }
585
586    fn visit_param(&mut self, p: &'v ast::Param) {
587        self.record("Param", None, p);
588        ast_visit::walk_param(self, p)
589    }
590
591    fn visit_arm(&mut self, a: &'v ast::Arm) {
592        self.record("Arm", None, a);
593        ast_visit::walk_arm(self, a)
594    }
595
596    fn visit_pat(&mut self, p: &'v ast::Pat) {
597        record_variants!(
598            (self, p, p.kind, None, ast, Pat, PatKind),
599            [
600                Wild,
601                Ident,
602                Struct,
603                TupleStruct,
604                Or,
605                Path,
606                Tuple,
607                Box,
608                Deref,
609                Ref,
610                Expr,
611                Range,
612                Slice,
613                Rest,
614                Never,
615                Guard,
616                Paren,
617                MacCall,
618                Err
619            ]
620        );
621        ast_visit::walk_pat(self, p)
622    }
623
624    fn visit_expr(&mut self, e: &'v ast::Expr) {
625        #[rustfmt::skip]
626        record_variants!(
627            (self, e, e.kind, None, ast, Expr, ExprKind),
628            [
629                Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
630                If, While, ForLoop, Loop, Match, Closure, Block, Await, Use, TryBlock, Assign,
631                AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
632                InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet,
633                Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy
634            ]
635        );
636        ast_visit::walk_expr(self, e)
637    }
638
639    fn visit_ty(&mut self, t: &'v ast::Ty) {
640        record_variants!(
641            (self, t, t.kind, None, ast, Ty, TyKind),
642            [
643                Slice,
644                Array,
645                Ptr,
646                Ref,
647                PinnedRef,
648                BareFn,
649                UnsafeBinder,
650                Never,
651                Tup,
652                Path,
653                Pat,
654                TraitObject,
655                ImplTrait,
656                Paren,
657                Typeof,
658                Infer,
659                ImplicitSelf,
660                MacCall,
661                CVarArgs,
662                Dummy,
663                Err
664            ]
665        );
666
667        ast_visit::walk_ty(self, t)
668    }
669
670    fn visit_generic_param(&mut self, g: &'v ast::GenericParam) {
671        self.record("GenericParam", None, g);
672        ast_visit::walk_generic_param(self, g)
673    }
674
675    fn visit_where_predicate(&mut self, p: &'v ast::WherePredicate) {
676        record_variants!(
677            (self, p, &p.kind, None, ast, WherePredicate, WherePredicateKind),
678            [BoundPredicate, RegionPredicate, EqPredicate]
679        );
680        ast_visit::walk_where_predicate(self, p)
681    }
682
683    fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, _: Span, _: NodeId) {
684        self.record("FnDecl", None, fk.decl());
685        ast_visit::walk_fn(self, fk)
686    }
687
688    fn visit_assoc_item(&mut self, i: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
689        record_variants!(
690            (self, i, i.kind, None, ast, AssocItem, AssocItemKind),
691            [Const, Fn, Type, MacCall, Delegation, DelegationMac]
692        );
693        ast_visit::walk_assoc_item(self, i, ctxt);
694    }
695
696    fn visit_param_bound(&mut self, b: &'v ast::GenericBound, _ctxt: BoundKind) {
697        record_variants!(
698            (self, b, b, None, ast, GenericBound, GenericBound),
699            [Trait, Outlives, Use]
700        );
701        ast_visit::walk_param_bound(self, b)
702    }
703
704    fn visit_field_def(&mut self, s: &'v ast::FieldDef) {
705        self.record("FieldDef", None, s);
706        ast_visit::walk_field_def(self, s)
707    }
708
709    fn visit_variant(&mut self, v: &'v ast::Variant) {
710        self.record("Variant", None, v);
711        ast_visit::walk_variant(self, v)
712    }
713
714    // `UseTree` has one inline use (in `ast::ItemKind::Use`) and one
715    // non-inline use (in `ast::UseTreeKind::Nested`). The former case is more
716    // common, so we don't implement `visit_use_tree` and tolerate the missed
717    // coverage in the latter case.
718
719    // `PathSegment` has one inline use (in `ast::ExprKind::MethodCall`) and
720    // one non-inline use (in `ast::Path::segments`). The latter case is more
721    // common than the former case, so we implement this visitor and tolerate
722    // the double counting in the former case.
723    fn visit_path_segment(&mut self, path_segment: &'v ast::PathSegment) {
724        self.record("PathSegment", None, path_segment);
725        ast_visit::walk_path_segment(self, path_segment)
726    }
727
728    // `GenericArgs` has one inline use (in `ast::AssocItemConstraint::gen_args`) and one
729    // non-inline use (in `ast::PathSegment::args`). The latter case is more
730    // common, so we implement `visit_generic_args` and tolerate the double
731    // counting in the former case.
732    fn visit_generic_args(&mut self, g: &'v ast::GenericArgs) {
733        record_variants!(
734            (self, g, g, None, ast, GenericArgs, GenericArgs),
735            [AngleBracketed, Parenthesized, ParenthesizedElided]
736        );
737        ast_visit::walk_generic_args(self, g)
738    }
739
740    fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
741        record_variants!(
742            (self, attr, attr.kind, None, ast, Attribute, AttrKind),
743            [Normal, DocComment]
744        );
745        ast_visit::walk_attribute(self, attr)
746    }
747
748    fn visit_expr_field(&mut self, f: &'v ast::ExprField) {
749        self.record("ExprField", None, f);
750        ast_visit::walk_expr_field(self, f)
751    }
752
753    fn visit_crate(&mut self, krate: &'v ast::Crate) {
754        self.record("Crate", None, krate);
755        ast_visit::walk_crate(self, krate)
756    }
757
758    fn visit_inline_asm(&mut self, asm: &'v ast::InlineAsm) {
759        self.record("InlineAsm", None, asm);
760        ast_visit::walk_inline_asm(self, asm)
761    }
762}