cairo_lang_macro/types/
mod.rs

1use std::vec::IntoIter;
2
3mod conversion;
4mod expansions;
5mod token;
6
7pub use expansions::*;
8pub use token::*;
9
10/// Result of procedural macro code generation.
11#[derive(Debug, Clone)]
12pub struct ProcMacroResult {
13    pub token_stream: TokenStream,
14    pub aux_data: Option<AuxData>,
15    pub diagnostics: Vec<Diagnostic>,
16    pub full_path_markers: Vec<String>,
17}
18
19/// **Auxiliary data** returned by procedural macro code generation.
20///
21/// This struct can be used to collect additional information from the Cairo source code of
22/// compiled project.
23/// For instance, you can create a procedural macro that collects some information stored by
24/// the Cairo programmer as attributes in the project source code.
25///
26/// The auxiliary data struct stores `Vec<u8>` leaving the serialization and deserialization
27/// of the data as user responsibility. No assumptions regarding the serialization algorithm
28/// are made.
29///
30/// For instance, auxiliary data can be serialized as JSON.
31///
32/// ```
33/// use cairo_lang_macro::{AuxData, ProcMacroResult, TokenStream, TokenTree, Token, TextSpan, attribute_macro, post_process, PostProcessContext};
34/// use serde::{Serialize, Deserialize};
35/// #[derive(Debug, Serialize, Deserialize)]
36/// struct SomeAuxDataFormat {
37///     some_message: String
38/// }
39///
40/// #[attribute_macro]
41/// pub fn some_macro(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult {
42///     // Remove macro call to avoid infinite loop.
43///     let code = token_stream.to_string().replace("#[some]", "");
44///     let token_stream = TokenStream::new(vec![
45///         TokenTree::Ident(
46///             Token::new(
47///                 &code,
48///                 TextSpan::new(0, code.len() as u32)
49///             )
50///         )
51///     ]);
52///     let value = SomeAuxDataFormat { some_message: "Hello from some macro!".to_string() };
53///     let value = serde_json::to_string(&value).unwrap();
54///     let value: Vec<u8> = value.into_bytes();
55///     let aux_data = AuxData::new(value);
56///     ProcMacroResult::new(token_stream).with_aux_data(aux_data)
57/// }
58///
59/// #[post_process]
60/// pub fn callback(context: PostProcessContext) {
61///     let aux_data = context.aux_data.into_iter()
62///         .map(|aux_data| {
63///             let value: Vec<u8> = aux_data.into();
64///             let aux_data: SomeAuxDataFormat = serde_json::from_slice(&value).unwrap();
65///             aux_data
66///         })
67///         .collect::<Vec<_>>();
68///     println!("{:?}", aux_data);
69/// }
70/// ```
71///
72/// All auxiliary data emitted during compilation can be consumed
73/// in the `post_process` implementation.
74#[derive(Debug, Clone)]
75pub struct AuxData(Vec<u8>);
76
77impl AuxData {
78    /// Create new [`AuxData`] struct from serialized data.
79    pub fn new(data: Vec<u8>) -> Self {
80        Self(data)
81    }
82}
83
84impl From<&[u8]> for AuxData {
85    fn from(bytes: &[u8]) -> Self {
86        Self(bytes.to_vec())
87    }
88}
89
90impl From<AuxData> for Vec<u8> {
91    fn from(aux_data: AuxData) -> Vec<u8> {
92        aux_data.0
93    }
94}
95
96/// Diagnostic returned by the procedural macro.
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub struct Diagnostic {
100    /// A human addressed message associated with the [`Diagnostic`].
101    ///
102    /// This message will not be parsed by the compiler,
103    /// but rather shown to the user as an explanation.
104    message: String,
105    /// The severity of the [`Diagnostic`].
106    ///
107    /// Defines how this diagnostic should influence the compilation.
108    severity: Severity,
109    /// Optional custom span for the diagnostic location.
110    ///
111    /// If provided, this span will be used instead of call site span.
112    /// This allows macros to point to specific elements within the annotated item.
113    span: Option<TextSpan>,
114}
115
116/// The severity of a diagnostic.
117///
118/// This should be roughly equivalent to the severity of Cairo diagnostics.
119///
120/// The appropriate action for each diagnostic kind will be taken by `Scarb`.
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
123pub enum Severity {
124    /// An error has occurred.
125    ///
126    /// Emitting diagnostic with [`Severity::Error`] severity will fail the source code compilation.
127    Error = 1,
128    /// A warning suggestion will be shown to the user.
129    ///
130    /// Emitting diagnostic with [`Severity::Warning`] severity does not stop the compilation.
131    Warning = 2,
132}
133
134/// A set of diagnostics that arose during the computation.
135#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub struct Diagnostics(Vec<Diagnostic>);
137
138impl Diagnostic {
139    /// Create new diagnostic with the given severity and message.
140    pub fn new(level: Severity, message: impl ToString) -> Self {
141        Self {
142            message: message.to_string(),
143            severity: level,
144            span: None,
145        }
146    }
147
148    /// Creates new diagnostic with the given span, severity level, and message.
149    pub fn spanned(span: TextSpan, level: Severity, message: impl ToString) -> Self {
150        let span = if span.start > span.end {
151            TextSpan::new(span.end, span.start)
152        } else {
153            span
154        };
155        Self {
156            message: message.to_string(),
157            severity: level,
158            span: Some(span),
159        }
160    }
161
162    /// Create new diagnostic with severity [`Severity::Error`].
163    pub fn error(message: impl ToString) -> Self {
164        Self {
165            message: message.to_string(),
166            severity: Severity::Error,
167            span: None,
168        }
169    }
170
171    /// Create new diagnostic with severity [`Severity::Warning`].
172    pub fn warn(message: impl ToString) -> Self {
173        Self {
174            message: message.to_string(),
175            severity: Severity::Warning,
176            span: None,
177        }
178    }
179
180    /// Create new error diagnostic with severity [`Severity::Error`], and the given span and message.
181    pub fn span_error(span: TextSpan, message: impl ToString) -> Self {
182        Self::spanned(span, Severity::Error, message)
183    }
184
185    /// Create new warning diagnostic with severity [`Severity::Warning`], and the given span and message.
186    pub fn span_warning(span: TextSpan, message: impl ToString) -> Self {
187        Self::spanned(span, Severity::Warning, message)
188    }
189
190    pub fn span(&self) -> Option<TextSpan> {
191        self.span.clone()
192    }
193
194    pub fn message(&self) -> &str {
195        &self.message
196    }
197
198    pub fn severity(&self) -> Severity {
199        self.severity
200    }
201}
202
203impl From<Vec<Diagnostic>> for Diagnostics {
204    fn from(diagnostics: Vec<Diagnostic>) -> Self {
205        Self(diagnostics)
206    }
207}
208
209impl From<Diagnostic> for Diagnostics {
210    fn from(diagnostics: Diagnostic) -> Self {
211        Self(vec![diagnostics])
212    }
213}
214
215impl Diagnostics {
216    /// Create new [`Diagnostics`] from a vector of diagnostics.
217    pub fn new(diagnostics: Vec<Diagnostic>) -> Self {
218        Self(diagnostics)
219    }
220
221    /// Create new diagnostic with severity [`Severity::Error`]
222    /// and push to the vector.
223    pub fn error(mut self, message: impl ToString) -> Self {
224        self.0.push(Diagnostic::error(message));
225        self
226    }
227
228    /// Create new diagnostic with severity [`Severity::Warning`]
229    /// and push to the vector.
230    pub fn warn(mut self, message: impl ToString) -> Self {
231        self.0.push(Diagnostic::warn(message));
232        self
233    }
234
235    /// Create new diagnostic with severity [`Severity::Error`] and the given span
236    /// and push to the vector.
237    pub fn span_error(mut self, span: TextSpan, message: impl ToString) -> Self {
238        self.0.push(Diagnostic::span_error(span, message));
239        self
240    }
241
242    /// Create new diagnostic with severity [`Severity::Warning`] and the given span
243    /// and push to the vector.
244    pub fn span_warning(mut self, span: TextSpan, message: impl ToString) -> Self {
245        self.0.push(Diagnostic::span_warning(span, message));
246        self
247    }
248}
249
250impl IntoIterator for Diagnostics {
251    type Item = Diagnostic;
252    type IntoIter = IntoIter<Self::Item>;
253
254    fn into_iter(self) -> IntoIter<Diagnostic> {
255        self.0.into_iter()
256    }
257}
258
259impl FromIterator<Diagnostic> for Diagnostics {
260    fn from_iter<T: IntoIterator<Item = Diagnostic>>(iter: T) -> Self {
261        Self(iter.into_iter().collect())
262    }
263}
264
265impl Extend<Diagnostic> for Diagnostics {
266    fn extend<T: IntoIterator<Item = Diagnostic>>(&mut self, iter: T) {
267        self.0.extend(iter);
268    }
269}
270
271impl ProcMacroResult {
272    /// Create new [`ProcMacroResult`], empty diagnostics set.
273    pub fn new(token_stream: TokenStream) -> Self {
274        Self {
275            token_stream,
276            aux_data: Default::default(),
277            diagnostics: Default::default(),
278            full_path_markers: Default::default(),
279        }
280    }
281
282    /// Set [`AuxData`] on the [`ProcMacroResult`].
283    pub fn with_aux_data(mut self, aux_data: AuxData) -> Self {
284        self.aux_data = Some(aux_data);
285        self
286    }
287
288    /// Append full path markers to the [`ProcMacroResult`].
289    pub fn with_full_path_markers(mut self, full_path_markers: Vec<String>) -> Self {
290        self.full_path_markers.extend(full_path_markers);
291        self
292    }
293
294    /// Append diagnostics to the [`ProcMacroResult`] diagnostics set.
295    pub fn with_diagnostics(mut self, diagnostics: Diagnostics) -> Self {
296        self.diagnostics.extend(diagnostics);
297        self
298    }
299}
300
301/// Input for the post-process callback.
302#[derive(Clone, Debug)]
303pub struct PostProcessContext {
304    /// Auxiliary data returned by the procedural macro.
305    pub aux_data: Vec<AuxData>,
306    /// Full path markers resolved by the host.
307    pub full_path_markers: Vec<FullPathMarker>,
308}
309
310/// Full path marker.
311///
312/// This contains information about full cairo path resolved by the host, identified by key.
313#[derive(Clone, Debug)]
314pub struct FullPathMarker {
315    /// Key returned by the procedural macro.
316    pub key: String,
317    /// Full path resolved by the host.
318    pub full_path: String,
319}
320
321#[cfg(test)]
322mod tests {
323    use crate::types::TokenStream;
324    use crate::{AllocationContext, TextSpan, Token, TokenTree};
325
326    #[test]
327    fn new_token_stream_metadata_empty() {
328        let token_stream = TokenStream::empty();
329        assert!(token_stream.metadata.file_id.is_none());
330        assert!(token_stream.metadata.original_file_path.is_none());
331    }
332
333    #[test]
334    fn can_convert_to_stable() {
335        let token_stream = TokenStream::new(vec![
336            TokenTree::Ident(Token::new("test", TextSpan::new(0, 4))),
337            TokenTree::Ident(Token::new(";", TextSpan::new(4, 5))),
338        ]);
339        let stable = token_stream.as_stable();
340        let ctx = AllocationContext::default();
341        let token_stream = unsafe { TokenStream::from_stable_in(&stable, &ctx) };
342        assert_eq!(token_stream.tokens.len(), 2);
343        assert_eq!(token_stream.to_string(), "test;");
344    }
345
346    #[test]
347    fn can_store_null_character() {
348        let token_stream = TokenStream::new(vec![TokenTree::Ident(Token::new(
349            "te\0st",
350            TextSpan::new(0, 4),
351        ))]);
352        let stable = token_stream.as_stable();
353        let ctx = AllocationContext::default();
354        let token_stream = unsafe { TokenStream::from_stable_in(&stable, &ctx) };
355        assert_eq!(token_stream.tokens.len(), 1);
356        assert_eq!(token_stream.to_string(), "te\0st");
357    }
358}