rustc_expand/
stats.rs

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