| use std::iter; | |
| use rustc_ast::{self as ast, DUMMY_NODE_ID, Expr, ExprKind}; | |
| use rustc_ast_pretty::pprust; | |
| use rustc_span::hygiene::{ExpnKind, MacroKind}; | |
| use rustc_span::{Span, Symbol, kw, sym}; | |
| use smallvec::SmallVec; | |
| use crate::base::{Annotatable, ExtCtxt}; | |
| use crate::expand::{AstFragment, AstFragmentKind}; | |
| #[derive(Default)] | |
| pub struct MacroStat { | |
| /// Number of uses of the macro. | |
| pub uses: usize, | |
| /// Number of lines of code (when pretty-printed). | |
| pub lines: usize, | |
| /// Number of bytes of code (when pretty-printed). | |
| pub bytes: usize, | |
| } | |
| pub(crate) fn elems_to_string<T>(elems: &SmallVec<[T; 1]>, f: impl Fn(&T) -> String) -> String { | |
| let mut s = String::new(); | |
| for (i, elem) in elems.iter().enumerate() { | |
| if i > 0 { | |
| s.push('\n'); | |
| } | |
| s.push_str(&f(elem)); | |
| } | |
| s | |
| } | |
| fn fragment_to_string(fragment: &AstFragment) -> String { | |
| match fragment { | |
| AstFragment::OptExpr(Some(expr)) | |
| | AstFragment::MethodReceiverExpr(expr) | |
| | AstFragment::Expr(expr) => pprust::expr_to_string(expr), | |
| AstFragment::Pat(ast) => pprust::pat_to_string(ast), | |
| AstFragment::Ty(ast) => pprust::ty_to_string(ast), | |
| AstFragment::Stmts(ast) => elems_to_string(ast, pprust::stmt_to_string), | |
| AstFragment::Items(ast) => elems_to_string(ast, |ast| pprust::item_to_string(ast)), | |
| AstFragment::TraitItems(ast) | |
| | AstFragment::ImplItems(ast) | |
| | AstFragment::TraitImplItems(ast) => { | |
| elems_to_string(ast, |ast| pprust::assoc_item_to_string(ast)) | |
| } | |
| AstFragment::ForeignItems(ast) => { | |
| elems_to_string(ast, |ast| pprust::foreign_item_to_string(ast)) | |
| } | |
| AstFragment::OptExpr(None) | |
| | AstFragment::Crate(_) | |
| | AstFragment::Arms(_) | |
| | AstFragment::ExprFields(_) | |
| | AstFragment::PatFields(_) | |
| | AstFragment::GenericParams(_) | |
| | AstFragment::Params(_) | |
| | AstFragment::FieldDefs(_) | |
| | AstFragment::Variants(_) | |
| | AstFragment::WherePredicates(_) => unreachable!(), | |
| } | |
| } | |
| pub(crate) fn update_bang_macro_stats( | |
| ecx: &mut ExtCtxt<'_>, | |
| fragment_kind: AstFragmentKind, | |
| span: Span, | |
| mac: Box<ast::MacCall>, | |
| fragment: &AstFragment, | |
| ) { | |
| // Does this path match any of the include macros, e.g. `include!`? | |
| // Ignore them. They would have large numbers but are entirely | |
| // unsurprising and uninteresting. | |
| let is_include_path = mac.path == sym::include | |
| || mac.path == sym::include_bytes | |
| || mac.path == sym::include_str | |
| || mac.path == [sym::std, sym::include].as_slice() // std::include | |
| || mac.path == [sym::std, sym::include_bytes].as_slice() // std::include_bytes | |
| || mac.path == [sym::std, sym::include_str].as_slice(); // std::include_str | |
| if is_include_path { | |
| return; | |
| } | |
| // The call itself (e.g. `println!("hi")`) is the input. Need to wrap | |
| // `mac` in something printable; `ast::Expr` is as good as anything | |
| // else. | |
| let expr = Expr { | |
| id: DUMMY_NODE_ID, | |
| kind: ExprKind::MacCall(mac), | |
| span: Default::default(), | |
| attrs: Default::default(), | |
| tokens: None, | |
| }; | |
| let input = pprust::expr_to_string(&expr); | |
| // Get `mac` back out of `expr`. | |
| let ast::Expr { kind: ExprKind::MacCall(mac), .. } = expr else { unreachable!() }; | |
| update_macro_stats(ecx, MacroKind::Bang, fragment_kind, span, &mac.path, &input, fragment); | |
| } | |
| pub(crate) fn update_attr_macro_stats( | |
| ecx: &mut ExtCtxt<'_>, | |
| fragment_kind: AstFragmentKind, | |
| span: Span, | |
| path: &ast::Path, | |
| attr: &ast::Attribute, | |
| item: Annotatable, | |
| fragment: &AstFragment, | |
| ) { | |
| // Does this path match `#[derive(...)]` in any of its forms? If so, | |
| // ignore it because the individual derives will go through the | |
| // `Invocation::Derive` handling separately. | |
| let is_derive_path = *path == sym::derive | |
| // ::core::prelude::v1::derive | |
| || *path == [kw::PathRoot, sym::core, sym::prelude, sym::v1, sym::derive].as_slice(); | |
| if is_derive_path { | |
| return; | |
| } | |
| // The attribute plus the item itself constitute the input, which we | |
| // measure. | |
| let input = format!( | |
| "{}\n{}", | |
| pprust::attribute_to_string(attr), | |
| fragment_to_string(&fragment_kind.expect_from_annotatables(iter::once(item))), | |
| ); | |
| update_macro_stats(ecx, MacroKind::Attr, fragment_kind, span, path, &input, fragment); | |
| } | |
| pub(crate) fn update_derive_macro_stats( | |
| ecx: &mut ExtCtxt<'_>, | |
| fragment_kind: AstFragmentKind, | |
| span: Span, | |
| path: &ast::Path, | |
| fragment: &AstFragment, | |
| ) { | |
| // Use something like `#[derive(Clone)]` for the measured input, even | |
| // though it may have actually appeared in a multi-derive attribute | |
| // like `#[derive(Clone, Copy, Debug)]`. | |
| let input = format!("#[derive({})]", pprust::path_to_string(path)); | |
| update_macro_stats(ecx, MacroKind::Derive, fragment_kind, span, path, &input, fragment); | |
| } | |
| pub(crate) fn update_macro_stats( | |
| ecx: &mut ExtCtxt<'_>, | |
| macro_kind: MacroKind, | |
| fragment_kind: AstFragmentKind, | |
| span: Span, | |
| path: &ast::Path, | |
| input: &str, | |
| fragment: &AstFragment, | |
| ) { | |
| // Measure the size of the output by pretty-printing it and counting | |
| // the lines and bytes. | |
| let name = Symbol::intern(&pprust::path_to_string(path)); | |
| let output = fragment_to_string(fragment); | |
| let num_lines = output.trim_end().split('\n').count(); | |
| let num_bytes = output.len(); | |
| // This code is useful for debugging `-Zmacro-stats`. For every | |
| // invocation it prints the full input and output. | |
| if false { | |
| let name = ExpnKind::Macro(macro_kind, name).descr(); | |
| let crate_name = &ecx.ecfg.crate_name; | |
| let span = ecx.sess.source_map().span_to_diagnostic_string(span); | |
| eprint!( | |
| "\ | |
| -------------------------------\n\ | |
| {name}: [{crate_name}] ({fragment_kind:?}) {span}\n\ | |
| -------------------------------\n\ | |
| {input}\n\ | |
| -- {num_lines} lines, {num_bytes} bytes --\n\ | |
| {output}\n\ | |
| " | |
| ); | |
| } | |
| // The recorded size is the difference between the input and the output. | |
| let entry = ecx.macro_stats.entry((name, macro_kind)).or_default(); | |
| entry.uses += 1; | |
| entry.lines += num_lines; | |
| entry.bytes += num_bytes; | |
| } |