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}