1use quote::{format_ident, quote, quote_spanned};
2use scx_stats::{StatsData, StatsKind, StatsMetaAux};
3use std::sync::atomic::{AtomicU64, Ordering};
4use syn::parse_macro_input;
5use syn::spanned::Spanned;
6use syn::{Attribute, Data, DeriveInput, Fields, Lit};
7
8static ASSERT_IDX: AtomicU64 = AtomicU64::new(0);
9
10#[proc_macro_derive(Stats, attributes(stat))]
11pub fn stat(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
12 let stats_aux = parse_macro_input!(input as StatsMetaAux);
13 let (meta, ident, paths) = (stats_aux.meta, stats_aux.ident, stats_aux.paths);
14
15 let mut output = proc_macro2::TokenStream::new();
16
17 for (_fname, field) in meta.fields.iter() {
18 match &field.data {
19 StatsData::Datum(datum)
20 | StatsData::Array(datum)
21 | StatsData::Dict { key: _, datum } => {
22 if let StatsKind::Struct(name) = &datum {
23 let path = &paths[name.as_str()];
24 let idx = ASSERT_IDX.fetch_add(1, Ordering::Relaxed);
25 let assert_id = format_ident!("_AssertStatsMeta_{}", idx);
26 #[rustfmt::skip]
27 let assert = quote_spanned! {path.span()=>
28 struct #assert_id where #path: scx_stats::Meta;
29 };
30 output.extend(assert.into_iter());
31 }
32 }
33 }
34 }
35
36 let body = serde_json::to_string(&meta).unwrap();
37 let trait_body = quote! {
38 #[rustfmt::skip]
39 impl scx_stats::Meta for #ident {
40 fn meta() -> scx_stats::StatsMeta {
41 let body = #body;
42 scx_stats::serde_json::from_str(body).unwrap()
43 }
44 }
45 };
46 output.extend(trait_body);
47
48 output.into()
49}
50
51#[proc_macro_attribute]
52pub fn stat_doc(
53 _attr: proc_macro::TokenStream,
54 item: proc_macro::TokenStream,
55) -> proc_macro::TokenStream {
56 let input = parse_macro_input!(item as DeriveInput);
57 let ident = input.ident;
58 let vis = input.vis;
59 let attrs = input.attrs;
60 let generics = input.generics;
61 let data = input.data;
62
63 let mut output = proc_macro2::TokenStream::new();
64
65 if let Data::Struct(data_struct) = data {
66 let fields = match data_struct.fields {
67 Fields::Named(fields_named) => fields_named.named,
68 _ => {
69 return syn::Error::new_spanned(
70 ident,
71 "stat attribute can only be used on structs with named fields",
72 )
73 .to_compile_error()
74 .into();
75 }
76 };
77
78 let mut new_fields = Vec::new();
79
80 for mut field in fields {
81 let mut doc_string = None;
82 let mut new_attrs = Vec::new();
83
84 for attr in field.attrs.clone() {
85 if attr.path().is_ident("stat") {
86 attr.parse_nested_meta(|meta| {
88 if meta.path.is_ident("desc") {
89 let desc_literal: Lit = meta.value()?.parse()?;
91 if let Lit::Str(lit_str) = desc_literal {
92 doc_string = Some(lit_str.value());
93 }
94 }
95 Ok(())
96 })
97 .unwrap_or_else(|err| {
98 panic!("Failed to parse the stat attribute: {}", err);
99 });
100 }
101 new_attrs.push(attr);
102 }
103
104 if let Some(description) = doc_string {
106 let doc_attr = Attribute {
107 pound_token: syn::token::Pound::default(),
108 style: syn::AttrStyle::Outer,
109 bracket_token: syn::token::Bracket::default(),
110 meta: syn::Meta::NameValue(syn::MetaNameValue {
111 path: syn::Path::from(format_ident!("doc")),
112 eq_token: syn::token::Eq::default(),
113 value: syn::Expr::Lit(syn::ExprLit {
114 lit: Lit::Str(syn::LitStr::new(&description, field.span())),
115 attrs: vec![],
116 }),
117 }),
118 };
119 new_attrs.push(doc_attr);
120 }
121
122 field.attrs = new_attrs;
123 new_fields.push(field);
124 }
125
126 let struct_def = quote! {
128 #(#attrs)*
129 #vis struct #ident #generics {
130 #(#new_fields),*
131 }
132 };
133
134 output.extend(struct_def);
135 return output.into();
136 }
137
138 syn::Error::new_spanned(
140 ident,
141 "stat attribute can only be used on structs with named fields",
142 )
143 .to_compile_error()
144 .into()
145}