1#![warn(missing_docs)]
6
7use proc_macro::TokenStream;
8use proc_macro_crate::{FoundCrate, crate_name};
9use quote::{ToTokens, quote};
10use syn::{
11 Attribute, Ident, Item, ItemEnum, ItemFn, ItemStruct, LitStr, Result, Token,
12 parse::{Parse, ParseStream},
13 parse_macro_input,
14};
15
16#[proc_macro_attribute]
24pub fn trace(attr: TokenStream, item: TokenStream) -> TokenStream {
25 let options = parse_macro_input!(attr as MacroOptions);
26
27 let mut function = parse_macro_input!(item as ItemFn);
28 let original_function = function.clone();
29 let ident = function.sig.ident.clone();
30 let dbgflow = dbgflow_crate_path();
31 let label = options.label_or(&ident);
32 let source = formatted_function_source(&original_function);
33
34 let argument_values = function.sig.inputs.iter().map(|arg| match arg {
35 syn::FnArg::Receiver(_) => {
36 quote! { #dbgflow::runtime::preview_argument("self", &self) }
37 }
38 syn::FnArg::Typed(pat_type) => {
39 let pat = &pat_type.pat;
40 let name = pat.to_token_stream().to_string();
41 match pat.as_ref() {
42 syn::Pat::Ident(pat_ident) => {
43 let binding = &pat_ident.ident;
44 quote! { #dbgflow::runtime::preview_argument(#name, &#binding) }
45 }
46 _ => quote! {
47 #dbgflow::ValueSlot {
48 name: #name.to_owned(),
49 preview: "<non-ident pattern>".to_owned(),
50 }
51 },
52 }
53 }
54 });
55
56 let block = &function.block;
57
58 if function.sig.asyncness.is_some() {
59 function.block = Box::new(syn::parse_quote!({
60 #dbgflow::runtime::trace_future(
61 #dbgflow::FunctionMeta {
62 id: concat!(module_path!(), "::", stringify!(#ident)),
63 label: #label,
64 module_path: module_path!(),
65 file: file!(),
66 line: line!(),
67 source: #source,
68 },
69 vec![#(#argument_values),*],
70 async move { #block }
71 ).await
72 }));
73 } else {
74 function.block = Box::new(syn::parse_quote!({
75 let mut __dbg_frame = #dbgflow::runtime::TraceFrame::enter(
76 #dbgflow::FunctionMeta {
77 id: concat!(module_path!(), "::", stringify!(#ident)),
78 label: #label,
79 module_path: module_path!(),
80 file: file!(),
81 line: line!(),
82 source: #source,
83 },
84 vec![#(#argument_values),*],
85 );
86 let __dbg_result = { #block };
87 __dbg_frame.finish_return(&__dbg_result);
88 __dbg_result
89 }));
90 }
91
92 quote!(#function).into()
93}
94
95#[proc_macro_attribute]
103pub fn ui_debug(attr: TokenStream, item: TokenStream) -> TokenStream {
104 let options = parse_macro_input!(attr as MacroOptions);
105
106 let item = parse_macro_input!(item as Item);
107 match item {
108 Item::Struct(item_struct) => expand_struct(item_struct, options).into(),
109 Item::Enum(item_enum) => expand_enum(item_enum, options).into(),
110 _ => syn::Error::new(
111 proc_macro2::Span::call_site(),
112 "#[ui_debug] supports structs and enums only",
113 )
114 .to_compile_error()
115 .into(),
116 }
117}
118
119#[proc_macro_attribute]
128pub fn dbg_test(attr: TokenStream, item: TokenStream) -> TokenStream {
129 let options = parse_macro_input!(attr as MacroOptions);
130
131 let mut function = parse_macro_input!(item as ItemFn);
132 let ident = function.sig.ident.clone();
133 let dbgflow = dbgflow_crate_path();
134 let label = options.label_or(&ident);
135
136 if function.sig.asyncness.is_some() {
137 return syn::Error::new_spanned(
138 &function.sig.ident,
139 "#[dbg_test] does not support async tests yet",
140 )
141 .to_compile_error()
142 .into();
143 }
144
145 if !function
146 .attrs
147 .iter()
148 .any(|attr| attr.path().is_ident("test"))
149 {
150 function.attrs.push(syn::parse_quote!(#[test]));
151 }
152
153 let test_name = format!("{}", ident);
154 let block = &function.block;
155 function.block = Box::new(syn::parse_quote!({
156 let __dbg_test_name = concat!(module_path!(), "::", #test_name);
157 #dbgflow::init_session(format!("dbgflow test: {}", __dbg_test_name));
158 #dbgflow::runtime::record_test_started_latest_with_label(__dbg_test_name, #label);
159
160 let __dbg_result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| #block));
161 match __dbg_result {
162 Ok(__dbg_value) => {
163 #dbgflow::runtime::record_test_passed_latest_with_label(__dbg_test_name, #label);
164 let _ = #dbgflow::persist_session_from_env(__dbg_test_name);
165 __dbg_value
166 }
167 Err(__dbg_panic) => {
168 #dbgflow::runtime::record_test_failed_latest_with_label(
169 __dbg_test_name,
170 #label,
171 #dbgflow::panic_message(&*__dbg_panic),
172 );
173 let _ = #dbgflow::persist_session_from_env(__dbg_test_name);
174 ::std::panic::resume_unwind(__dbg_panic)
175 }
176 }
177 }));
178
179 quote!(#function).into()
180}
181
182fn expand_struct(mut item: ItemStruct, options: MacroOptions) -> proc_macro2::TokenStream {
183 let source = formatted_struct_source(&item);
184 maybe_add_debug_derive(&mut item.attrs);
185 let ident = &item.ident;
186 let dbgflow = dbgflow_crate_path();
187 let label = options.label_or(ident);
188
189 quote! {
190 #item
191
192 impl #dbgflow::UiDebugValue for #ident {
193 fn ui_debug_type_meta() -> #dbgflow::TypeMeta {
194 #dbgflow::TypeMeta {
195 id: concat!(module_path!(), "::", stringify!(#ident)),
196 label: #label,
197 module_path: module_path!(),
198 file: file!(),
199 line: line!(),
200 source: #source,
201 }
202 }
203 }
204 }
205}
206
207fn expand_enum(mut item: ItemEnum, options: MacroOptions) -> proc_macro2::TokenStream {
208 let source = formatted_enum_source(&item);
209 maybe_add_debug_derive(&mut item.attrs);
210 let ident = &item.ident;
211 let dbgflow = dbgflow_crate_path();
212 let label = options.label_or(ident);
213
214 quote! {
215 #item
216
217 impl #dbgflow::UiDebugValue for #ident {
218 fn ui_debug_type_meta() -> #dbgflow::TypeMeta {
219 #dbgflow::TypeMeta {
220 id: concat!(module_path!(), "::", stringify!(#ident)),
221 label: #label,
222 module_path: module_path!(),
223 file: file!(),
224 line: line!(),
225 source: #source,
226 }
227 }
228 }
229 }
230}
231
232fn maybe_add_debug_derive(attrs: &mut Vec<Attribute>) {
233 let has_debug = attrs.iter().any(|attr| {
234 attr.path().is_ident("derive") && attr.meta.to_token_stream().to_string().contains("Debug")
235 });
236
237 if !has_debug {
238 attrs.push(syn::parse_quote!(#[derive(Debug)]));
239 }
240}
241
242fn dbgflow_crate_path() -> proc_macro2::TokenStream {
243 match crate_name("dbgflow") {
244 Ok(FoundCrate::Itself) => quote!(crate),
245 Ok(FoundCrate::Name(name)) => {
246 let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
247 quote!(::#ident)
248 }
249 Err(_) => quote!(::dbgflow),
250 }
251}
252
253#[derive(Default)]
254struct MacroOptions {
255 name: Option<LitStr>,
256}
257
258impl MacroOptions {
259 fn label_or(&self, fallback: &Ident) -> LitStr {
260 self.name
261 .clone()
262 .unwrap_or_else(|| LitStr::new(&fallback.to_string(), fallback.span()))
263 }
264}
265
266impl Parse for MacroOptions {
267 fn parse(input: ParseStream<'_>) -> Result<Self> {
268 if input.is_empty() {
269 return Ok(Self::default());
270 }
271
272 let key: Ident = input.parse()?;
273 input.parse::<Token![=]>()?;
274 let value: LitStr = input.parse()?;
275
276 if !input.is_empty() {
277 return Err(input.error("expected only `name = \"...\"`"));
278 }
279
280 if key != "name" {
281 return Err(syn::Error::new(
282 key.span(),
283 "supported options: `name = \"...\"`",
284 ));
285 }
286
287 Ok(Self { name: Some(value) })
288 }
289}
290
291fn formatted_function_source(function: &ItemFn) -> LitStr {
292 formatted_item_source(Item::Fn(function.clone()))
293}
294
295fn formatted_struct_source(item: &ItemStruct) -> LitStr {
296 formatted_item_source(Item::Struct(item.clone()))
297}
298
299fn formatted_enum_source(item: &ItemEnum) -> LitStr {
300 formatted_item_source(Item::Enum(item.clone()))
301}
302
303fn formatted_item_source(item: Item) -> LitStr {
304 let file = syn::File {
305 shebang: None,
306 attrs: Vec::new(),
307 items: vec![item],
308 };
309 let source = prettyplease::unparse(&file);
310 LitStr::new(&source, proc_macro2::Span::call_site())
311}