Skip to main content

cairo_lang_proc_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse::Parser;
4use syn::punctuated::Punctuated;
5use syn::{ItemFn, Meta, Token, parse_macro_input};
6
7mod debug;
8mod heap_size;
9mod rewriter;
10
11#[proc_macro_derive(DebugWithDb, attributes(debug_db, hide_field_debug_with_db))]
12pub fn derive_debug_with_db(input: TokenStream) -> TokenStream {
13    debug::derive_debug_with_db(input)
14}
15
16#[proc_macro_derive(HeapSize)]
17pub fn derive_heap_size(input: TokenStream) -> TokenStream {
18    heap_size::derive_heap_size(input)
19}
20
21#[proc_macro_derive(SemanticObject, attributes(dont_rewrite))]
22pub fn derive_semantic_object(input: TokenStream) -> TokenStream {
23    rewriter::derive_semantic_object(input)
24}
25
26/// ──────────────────────────────────────────────────────────────────────────────
27/// Test macro
28/// ──────────────────────────────────────────────────────────────────────────────
29/// A test attribute macro that initializes logging before running a test.
30/// This replaces the `test_log::test` macro with Cairo's own logging initialization.
31///
32/// # Example
33/// ```ignore
34/// use cairo_lang_proc_macros::test;
35///
36/// #[test]
37/// fn my_test() {
38///     // Test code here
39/// }
40/// ```
41#[proc_macro_attribute]
42pub fn test(_attr: TokenStream, item: TokenStream) -> TokenStream {
43    let input_fn = parse_macro_input!(item as ItemFn);
44
45    let _fn_name = &input_fn.sig.ident;
46    let fn_vis = &input_fn.vis;
47    let fn_sig = &input_fn.sig;
48    let fn_block = &input_fn.block;
49    let fn_attrs = &input_fn.attrs;
50
51    // Generate the test function with logging initialization
52    let output = quote! {
53        #[::core::prelude::v1::test]
54        #(#fn_attrs)*
55        #fn_vis #fn_sig {
56            // Initialize logging with ERROR level for tests
57            cairo_lang_test_utils::logging::init_logging(cairo_lang_test_utils::logging::level::ERROR);
58
59            // Run the original test body
60            #fn_block
61        }
62    };
63
64    TokenStream::from(output)
65}
66
67#[proc_macro_attribute]
68pub fn interned(attr: TokenStream, item: TokenStream) -> TokenStream {
69    let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
70    #[allow(unused_mut)]
71    let mut args: Punctuated<Meta, Token![,]> =
72        if attr.is_empty() { Punctuated::new() } else { parser.parse(attr).unwrap() };
73
74    let has_heap_size = args
75        .iter()
76        .any(|meta| matches!(meta, Meta::NameValue(nv) if nv.path.is_ident("heap_size")));
77
78    if !has_heap_size {
79        let heap_size: Meta = syn::parse_quote!(heap_size = cairo_lang_utils::HeapSize::heap_size);
80        args.push(heap_size);
81    }
82
83    let salsa_attr = quote! { #[salsa::interned(#args)] };
84    let item: proc_macro2::TokenStream = item.into();
85    let output = quote! {
86        #salsa_attr
87        #item
88    };
89    output.into()
90}
91
92#[proc_macro_attribute]
93pub fn tracked(attr: TokenStream, item: TokenStream) -> TokenStream {
94    let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
95    #[allow(unused_mut)]
96    let mut args: Punctuated<Meta, Token![,]> =
97        if attr.is_empty() { Punctuated::new() } else { parser.parse(attr).unwrap() };
98
99    let has_heap_size = args
100        .iter()
101        .any(|meta| matches!(meta, Meta::NameValue(nv) if nv.path.is_ident("heap_size")));
102
103    if !has_heap_size {
104        let heap_size: Meta = syn::parse_quote!(heap_size = cairo_lang_utils::HeapSize::heap_size);
105        args.push(heap_size);
106    }
107
108    let salsa_attr = quote! { #[salsa::tracked(#args)] };
109    let item: proc_macro2::TokenStream = item.into();
110    let output = quote! {
111        #salsa_attr
112        #item
113    };
114    output.into()
115}