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 AUX_DATA_CALLBACKS: [fn(PostProcessContext)];
173
174/// The auxiliary data collection callback.
175///
176/// This function needs to be accessible through the FFI interface,
177/// of the dynamic library re-exporting it.
178///
179/// The function will be called for each procedural macro, regardless if it redefines the callback
180/// behaviour or not. In case no custom behaviour is defined, this is a no-op.
181///
182/// # Safety
183#[doc(hidden)]
184#[no_mangle]
185pub unsafe extern "C" fn post_process_callback_v2(
186    context: StablePostProcessContext,
187) -> StablePostProcessContext {
188    if !AUX_DATA_CALLBACKS.is_empty() {
189        // Callback has been defined, applying the aux data collection.
190        let context = PostProcessContext::from_stable(&context);
191        for fun in AUX_DATA_CALLBACKS {
192            fun(context.clone());
193        }
194    }
195    context
196}
197
198/// Return documentation string associated with this procedural macro expansion.
199///
200/// # Safety
201///
202#[doc(hidden)]
203#[no_mangle]
204pub unsafe extern "C" fn doc_v2(item_name: *mut c_char) -> *mut c_char {
205    let item_name = CStr::from_ptr(item_name).to_string_lossy().to_string();
206    let doc = MACRO_DEFINITIONS_SLICE
207        .iter()
208        .find_map(|m| {
209            if m.name == item_name.as_str() {
210                Some(m.doc)
211            } else {
212                None
213            }
214        })
215        .expect("procedural macro not found");
216    CString::new(doc).unwrap().into_raw()
217}
218
219/// Free the memory allocated for the documentation.
220///
221/// # Safety
222///
223#[doc(hidden)]
224#[no_mangle]
225pub unsafe extern "C" fn free_doc_v2(doc: *mut c_char) {
226    if !doc.is_null() {
227        let _ = CString::from_raw(doc);
228    }
229}
230
231/// A no-op Cairo attribute macro implementation.
232///
233/// This macro implementation does not produce any changes.
234/// Can be exposed as a placeholder macro for the internal purposes.
235#[doc(hidden)]
236pub fn no_op_attr(_attr: TokenStream, input: TokenStream) -> ProcMacroResult {
237    ProcMacroResult::new(input)
238}