Skip to main content

rustc_expand/
stats.rs

1use std::iter;
2
3use rustc_ast::{self as ast, DUMMY_NODE_ID, Expr, ExprKind};
4use rustc_ast_pretty::pprust;
5use rustc_span::hygiene::{ExpnKind, MacroKind};
6use rustc_span::{Span, Symbol, kw, sym};
7use smallvec::SmallVec;
8
9use crate::base::{Annotatable, ExtCtxt};
10use crate::expand::{AstFragment, AstFragmentKind};
11
12#[derive(#[automatically_derived]
impl ::core::default::Default for MacroStat {
    #[inline]
    fn default() -> MacroStat {
        MacroStat {
            uses: ::core::default::Default::default(),
            lines: ::core::default::Default::default(),
            bytes: ::core::default::Default::default(),
        }
    }
}Default)]
13pub struct MacroStat {
14    /// Number of uses of the macro.
15    pub uses: usize,
16
17    /// Number of lines of code (when pretty-printed).
18    pub lines: usize,
19
20    /// Number of bytes of code (when pretty-printed).
21    pub bytes: usize,
22}
23
24pub(crate) fn elems_to_string<T>(elems: &SmallVec<[T; 1]>, f: impl Fn(&T) -> String) -> String {
25    let mut s = String::new();
26    for (i, elem) in elems.iter().enumerate() {
27        if i > 0 {
28            s.push('\n');
29        }
30        s.push_str(&f(elem));
31    }
32    s
33}
34
35fn fragment_to_string(fragment: &AstFragment) -> String {
36    match fragment {
37        AstFragment::OptExpr(Some(expr))
38        | AstFragment::MethodReceiverExpr(expr)
39        | AstFragment::Expr(expr) => pprust::expr_to_string(expr),
40        AstFragment::Pat(ast) => pprust::pat_to_string(ast),
41        AstFragment::Ty(ast) => pprust::ty_to_string(ast),
42        AstFragment::Stmts(ast) => elems_to_string(ast, pprust::stmt_to_string),
43        AstFragment::Items(ast) => elems_to_string(ast, |ast| pprust::item_to_string(ast)),
44        AstFragment::TraitItems(ast)
45        | AstFragment::ImplItems(ast)
46        | AstFragment::TraitImplItems(ast) => {
47            elems_to_string(ast, |ast| pprust::assoc_item_to_string(ast))
48        }
49        AstFragment::ForeignItems(ast) => {
50            elems_to_string(ast, |ast| pprust::foreign_item_to_string(ast))
51        }
52        AstFragment::OptExpr(None)
53        | AstFragment::Crate(_)
54        | AstFragment::Arms(_)
55        | AstFragment::ExprFields(_)
56        | AstFragment::PatFields(_)
57        | AstFragment::GenericParams(_)
58        | AstFragment::Params(_)
59        | AstFragment::FieldDefs(_)
60        | AstFragment::Variants(_)
61        | AstFragment::WherePredicates(_) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
62    }
63}
64
65pub(crate) fn update_bang_macro_stats(
66    ecx: &mut ExtCtxt<'_>,
67    fragment_kind: AstFragmentKind,
68    span: Span,
69    mac: Box<ast::MacCall>,
70    fragment: &AstFragment,
71) {
72    // Does this path match any of the include macros, e.g. `include!`?
73    // Ignore them. They would have large numbers but are entirely
74    // unsurprising and uninteresting.
75    let is_include_path = mac.path == sym::include
76        || mac.path == sym::include_bytes
77        || mac.path == sym::include_str
78        || mac.path == [sym::std, sym::include].as_slice() // std::include
79        || mac.path == [sym::std, sym::include_bytes].as_slice() // std::include_bytes
80        || mac.path == [sym::std, sym::include_str].as_slice(); // std::include_str
81    if is_include_path {
82        return;
83    }
84
85    // The call itself (e.g. `println!("hi")`) is the input. Need to wrap
86    // `mac` in something printable; `ast::Expr` is as good as anything
87    // else.
88    let expr = Expr {
89        id: DUMMY_NODE_ID,
90        kind: ExprKind::MacCall(mac),
91        span: Default::default(),
92        attrs: Default::default(),
93        tokens: None,
94    };
95    let input = pprust::expr_to_string(&expr);
96
97    // Get `mac` back out of `expr`.
98    let ast::Expr { kind: ExprKind::MacCall(mac), .. } = expr else { ::core::panicking::panic("internal error: entered unreachable code")unreachable!() };
99
100    update_macro_stats(ecx, MacroKind::Bang, fragment_kind, span, &mac.path, &input, fragment);
101}
102
103pub(crate) fn update_attr_macro_stats(
104    ecx: &mut ExtCtxt<'_>,
105    fragment_kind: AstFragmentKind,
106    span: Span,
107    path: &ast::Path,
108    attr: &ast::Attribute,
109    item: Annotatable,
110    fragment: &AstFragment,
111) {
112    // Does this path match `#[derive(...)]` in any of its forms? If so,
113    // ignore it because the individual derives will go through the
114    // `Invocation::Derive` handling separately.
115    let is_derive_path = *path == sym::derive
116        // ::core::prelude::v1::derive
117        || *path == [kw::PathRoot, sym::core, sym::prelude, sym::v1, sym::derive].as_slice();
118    if is_derive_path {
119        return;
120    }
121
122    // The attribute plus the item itself constitute the input, which we
123    // measure.
124    let input = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}\n{1}",
                pprust::attribute_to_string(attr),
                fragment_to_string(&fragment_kind.expect_from_annotatables(iter::once(item)))))
    })format!(
125        "{}\n{}",
126        pprust::attribute_to_string(attr),
127        fragment_to_string(&fragment_kind.expect_from_annotatables(iter::once(item))),
128    );
129    update_macro_stats(ecx, MacroKind::Attr, fragment_kind, span, path, &input, fragment);
130}
131
132pub(crate) fn update_derive_macro_stats(
133    ecx: &mut ExtCtxt<'_>,
134    fragment_kind: AstFragmentKind,
135    span: Span,
136    path: &ast::Path,
137    fragment: &AstFragment,
138) {
139    // Use something like `#[derive(Clone)]` for the measured input, even
140    // though it may have actually appeared in a multi-derive attribute
141    // like `#[derive(Clone, Copy, Debug)]`.
142    let input = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#[derive({0})]",
                pprust::path_to_string(path)))
    })format!("#[derive({})]", pprust::path_to_string(path));
143    update_macro_stats(ecx, MacroKind::Derive, fragment_kind, span, path, &input, fragment);
144}
145
146pub(crate) fn update_macro_stats(
147    ecx: &mut ExtCtxt<'_>,
148    macro_kind: MacroKind,
149    fragment_kind: AstFragmentKind,
150    span: Span,
151    path: &ast::Path,
152    input: &str,
153    fragment: &AstFragment,
154) {
155    // Measure the size of the output by pretty-printing it and counting
156    // the lines and bytes.
157    let name = Symbol::intern(&pprust::path_to_string(path));
158    let output = fragment_to_string(fragment);
159    let num_lines = output.trim_end().split('\n').count();
160    let num_bytes = output.len();
161
162    // This code is useful for debugging `-Zmacro-stats`. For every
163    // invocation it prints the full input and output.
164    if false {
165        let name = ExpnKind::Macro(macro_kind, name).descr();
166        let crate_name = &ecx.ecfg.crate_name;
167        let span = ecx.sess.source_map().span_to_diagnostic_string(span);
168        {
    ::std::io::_eprint(format_args!("-------------------------------\n{0}: [{1}] ({2:?}) {3}\n-------------------------------\n{4}\n-- {5} lines, {6} bytes --\n{7}\n",
            name, crate_name, fragment_kind, span, input, num_lines,
            num_bytes, output));
};eprint!(
169            "\
170            -------------------------------\n\
171            {name}: [{crate_name}] ({fragment_kind:?}) {span}\n\
172            -------------------------------\n\
173            {input}\n\
174            -- {num_lines} lines, {num_bytes} bytes --\n\
175            {output}\n\
176        "
177        );
178    }
179
180    // The recorded size is the difference between the input and the output.
181    let entry = ecx.macro_stats.entry((name, macro_kind)).or_default();
182    entry.uses += 1;
183    entry.lines += num_lines;
184    entry.bytes += num_bytes;
185}