cairo_lang_macro/
lib.rs

1//! <br>
2//!
3//! **A library for writing Cairo procedural macros in Rust.**
4//! <br>
5//!
6//! # Cairo procedural macro
7//!
8//! A Cairo procedural macro is a dynamic library that can be loaded by
9//! [Scarb](https://github.com/software-mansion/scarb) package manager during the project build.
10//! The goal of procedural macros is to provide dynamic code generation capabilities to Cairo
11//! programmers.
12//!
13//! The code generation should be implemented as a Rust function, that takes [`TokenStream`] as
14//! input and returns [`ProcMacroResult`] as output.
15//! The function implementing the macro should be wrapped with [`attribute_macro`].
16//!
17
18pub use cairo_lang_macro_attributes::*;
19pub use cairo_lang_quote::*;
20
21#[doc(hidden)]
22pub use linkme;
23
24use std::cell::RefCell;
25
26use cairo_lang_macro_stable::ffi::StableSlice;
27use cairo_lang_macro_stable::{
28    StableExpansionsList, StablePostProcessContext, StableProcMacroResult, StableTextSpan,
29};
30use std::ffi::{CStr, CString, c_char};
31use std::num::NonZeroU8;
32use std::ops::Deref;
33
34mod types;
35pub use types::*;
36
37#[no_mangle]
38pub static CAIRO_LANG_MACRO_API_VERSION: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(2) };
39
40// A thread-local allocation context for allocating tokens on proc macro side.
41thread_local!(static CONTEXT: RefCell<AllocationContext> =  RefCell::default() );
42
43thread_local!(static CALL_SITE: RefCell<(u32, u32)> = RefCell::default());
44
45#[doc(hidden)]
46#[derive(Clone)]
47pub struct ExpansionDefinition {
48    pub name: &'static str,
49    pub doc: &'static str,
50    pub kind: ExpansionKind,
51    pub fun: ExpansionFunc,
52}
53
54#[derive(Clone)]
55pub enum ExpansionFunc {
56    Attr(fn(TokenStream, TokenStream) -> ProcMacroResult),
57    Other(fn(TokenStream) -> ProcMacroResult),
58}
59
60/// Distributed slice for storing procedural macro code expansion capabilities.
61///
62/// Each element denotes name of the macro, and the expand function pointer.
63#[doc(hidden)]
64#[linkme::distributed_slice]
65pub static MACRO_DEFINITIONS_SLICE: [ExpansionDefinition];
66
67/// This function discovers expansion capabilities defined by the procedural macro.
68///
69/// This function needs to be accessible through the FFI interface,
70/// of the dynamic library re-exporting it.
71///
72/// # Safety
73#[doc(hidden)]
74#[no_mangle]
75pub unsafe extern "C" fn list_expansions_v2() -> StableExpansionsList {
76    let list = MACRO_DEFINITIONS_SLICE
77        .iter()
78        .map(|m| m.clone().into_stable())
79        .collect();
80    StableSlice::new(list)
81}
82
83/// Free the memory allocated for the [`StableProcMacroResult`].
84///
85/// This function needs to be accessible through the FFI interface,
86/// of the dynamic library re-exporting it.
87///
88/// # Safety
89#[doc(hidden)]
90#[no_mangle]
91pub unsafe extern "C" fn free_expansions_list_v2(list: StableExpansionsList) {
92    let v = list.into_owned();
93    v.into_iter().for_each(|v| {
94        ExpansionDefinition::free_owned(v);
95    });
96}
97
98/// The code expansion callback.
99///
100/// This function needs to be accessible through the FFI interface,
101/// of the dynamic library re-exporting it.
102///
103/// The function will be called for each code expansion by the procedural macro.
104///
105/// # Safety
106#[doc(hidden)]
107#[no_mangle]
108pub unsafe extern "C" fn expand_v2(
109    item_name: *const c_char,
110    call_site: StableTextSpan,
111    stable_attr: cairo_lang_macro_stable::StableTokenStream,
112    stable_token_stream: cairo_lang_macro_stable::StableTokenStream,
113) -> cairo_lang_macro_stable::StableResultWrapper {
114    CONTEXT.with(|ctx_cell| {
115        // Read size hint from stable token stream. This will be used to create a sufficiently
116        // large bump allocation buffer.
117        let size_hint: usize = stable_token_stream.size_hint + stable_attr.size_hint;
118        // Replace the allocation context with a new one.
119        // If there is no interned string guards, the old context will be de-allocated.
120        ctx_cell.replace(AllocationContext::with_capacity(size_hint));
121        let ctx_borrow = ctx_cell.borrow();
122        let ctx: &AllocationContext = ctx_borrow.deref();
123        // Set the call site for the current expand call.
124        CALL_SITE.replace((call_site.start, call_site.end));
125        // Copy the stable token stream into current context.
126        let token_stream = TokenStream::from_stable_in(&stable_token_stream, ctx);
127        let attr_token_stream = TokenStream::from_stable_in(&stable_attr, ctx);
128        let item_name = CStr::from_ptr(item_name)
129            .to_str()
130            .expect("item name must be a valid string");
131        let fun = MACRO_DEFINITIONS_SLICE
132            .iter()
133            .find_map(|m| {
134                if m.name == item_name {
135                    Some(m.fun.clone())
136                } else {
137                    None
138                }
139            })
140            .expect("procedural macro not found");
141        let result = match fun {
142            ExpansionFunc::Attr(fun) => fun(attr_token_stream, token_stream),
143            ExpansionFunc::Other(fun) => fun(token_stream),
144        };
145        let result: StableProcMacroResult = result.into_stable();
146        cairo_lang_macro_stable::StableResultWrapper {
147            input: stable_token_stream,
148            input_attr: stable_attr,
149            output: result,
150        }
151    })
152}
153
154/// Free the memory allocated for the [`StableProcMacroResult`].
155///
156/// This function needs to be accessible through the FFI interface,
157/// of the dynamic library re-exporting it.
158/// The name of this function will not be mangled by the Rust compiler (through the `no_mangle` attribute).
159/// This means that the name will not be extended with neither additional prefixes nor suffixes
160/// by the Rust compiler and the corresponding symbol will be available by the name of the function as id.
161///
162/// # Safety
163#[doc(hidden)]
164#[no_mangle]
165pub unsafe extern "C" fn free_result_v2(result: StableProcMacroResult) {
166    ProcMacroResult::free_owned_stable(result);
167}
168
169/// Distributed slice for storing auxiliary data collection callback pointers.
170#[doc(hidden)]
171#[linkme::distributed_slice]
172pub static CALLBACKS: [Callback];
173
174#[doc(hidden)]
175#[derive(Clone)]
176#[non_exhaustive]
177pub enum Callback {
178    PostProcess(fn(PostProcessContext)),
179    Fingerprint(fn() -> u64),
180}
181
182/// The auxiliary data collection callback.
183///
184/// This function needs to be accessible through the FFI interface,
185/// of the dynamic library re-exporting it.
186///
187/// The function will be called for each procedural macro, regardless if it redefines the callback
188/// behaviour or not. In case no custom behaviour is defined, this is a no-op.
189///
190/// # Safety
191#[doc(hidden)]
192#[no_mangle]
193pub unsafe extern "C" fn post_process_callback_v2(
194    context: StablePostProcessContext,
195) -> StablePostProcessContext {
196    if !CALLBACKS.is_empty() {
197        // Callback has been defined, applying the aux data collection.
198        let context = PostProcessContext::from_stable(&context);
199        for callback in CALLBACKS {
200            if let Callback::PostProcess(fun) = callback {
201                fun(context.clone());
202            }
203        }
204    }
205    context
206}
207
208/// Return documentation string associated with this procedural macro expansion.
209///
210/// # Safety
211///
212#[doc(hidden)]
213#[no_mangle]
214pub unsafe extern "C" fn doc_v2(item_name: *mut c_char) -> *mut c_char {
215    let item_name = CStr::from_ptr(item_name).to_string_lossy().to_string();
216    let doc = MACRO_DEFINITIONS_SLICE
217        .iter()
218        .find_map(|m| {
219            if m.name == item_name.as_str() {
220                Some(m.doc)
221            } else {
222                None
223            }
224        })
225        .expect("procedural macro not found");
226    CString::new(doc).unwrap().into_raw()
227}
228
229/// Free the memory allocated for the documentation.
230///
231/// # Safety
232///
233#[doc(hidden)]
234#[no_mangle]
235pub unsafe extern "C" fn free_doc_v2(doc: *mut c_char) {
236    if !doc.is_null() {
237        let _ = CString::from_raw(doc);
238    }
239}
240
241/// The fingerprint calculation callback.
242///
243/// This function needs to be accessible through the FFI interface,
244/// of the dynamic library re-exporting it.
245///
246/// A fingerprint is an `u64` value used to determine if Cairo code depending on this
247/// procedural macro should be recompiled or can use the incremental cache artifacts from
248/// the previous build. User can define their own fingerprint callback, otherwise this will return
249/// a constant value by default.
250///
251/// The function will be called for each procedural macro, regardless if it redefines the callback
252/// behaviour or not.
253///
254/// # Safety
255#[doc(hidden)]
256#[no_mangle]
257pub unsafe extern "C" fn fingerprint_v2() -> u64 {
258    let mut result: u64 = 0;
259    for callback in CALLBACKS {
260        if let Callback::Fingerprint(fun) = callback {
261            // This formula for combining hash values is taken from `boost::hash_combine` implementation.
262            result ^= fun() + 0x9e3779b9 + (result << 6) + (result >> 2)
263        }
264    }
265    result
266}
267
268/// A no-op Cairo attribute macro implementation.
269///
270/// This macro implementation does not produce any changes.
271/// Can be exposed as a placeholder macro for the internal purposes.
272#[doc(hidden)]
273pub fn no_op_attr(_attr: TokenStream, input: TokenStream) -> ProcMacroResult {
274    ProcMacroResult::new(input)
275}