From e6688b57e37fdfbb5160bc22d81a746c9c32d109 Mon Sep 17 00:00:00 2001 From: maciektr Date: Thu, 21 Mar 2024 18:49:26 +0100 Subject: [PATCH] Rewrite cairo-lang-macro docs commit-id:1ca84df1 --- .../cairo-lang-macro-attributes/src/lib.rs | 24 +++-- plugins/cairo-lang-macro/src/lib.rs | 28 ++++- .../cairo-lang-macro/src/types/conversion.rs | 1 + .../cairo-lang-macro/src/types/expansions.rs | 1 + plugins/cairo-lang-macro/src/types/mod.rs | 101 +++++++++++++++++- scarb/tests/build_cairo_plugin.rs | 6 +- 6 files changed, 137 insertions(+), 24 deletions(-) diff --git a/plugins/cairo-lang-macro-attributes/src/lib.rs b/plugins/cairo-lang-macro-attributes/src/lib.rs index 821ee55ae..01d268ff2 100644 --- a/plugins/cairo-lang-macro-attributes/src/lib.rs +++ b/plugins/cairo-lang-macro-attributes/src/lib.rs @@ -3,12 +3,11 @@ use quote::quote; use scarb_stable_hash::short_hash; use syn::{parse_macro_input, ItemFn}; -/// Inline macro helper. +/// Constructs the attribute macro implementation. /// /// This macro hides the conversion to stable ABI structs from the user. /// -/// # Safety -/// Note that token stream deserialization may fail. +/// Note, that this macro can be used multiple times, to define multiple independent attribute macros. #[proc_macro_attribute] pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream { let item: ItemFn = parse_macro_input!(input as ItemFn); @@ -32,22 +31,25 @@ pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream { /// Constructs the post-processing callback. /// -/// The procedural macro can emit additional auxiliary data alongside the generated [`TokenStream`] -/// during the code expansion. This data can be used to collect additional information from the -/// source code of a project that is being compiled during the macro execution. +/// This callback will be called after the source code compilation (and thus after all the procedural +/// macro expansion calls). +/// The post-processing callback is the only function defined by the procedural macro that is +/// allowed to have side effects. +/// +/// This macro will be called with a list of all auxiliary data emitted by the macro during code expansion. +/// +/// This data can be used to collect additional information from the source code of a project +/// that is being compiled during the macro execution. /// For instance, you can create a procedural macro that collects some information stored by /// the Cairo programmer as attributes in the project source code. -/// -/// This should be used to implement a collection callback for the auxiliary data. /// This callback will be called after the source code compilation (and thus after all the procedural /// macro executions). All auxiliary data emitted by the procedural macro during source code compilation /// will be passed to the callback as an argument. /// -/// The callback can be used to process or persist the data collected during the compilation. -/// /// This macro hides the conversion to stable ABI structs from the user. /// -/// # Safety +/// If multiple callbacks are defined within the macro, all the implementations will be executed. +/// No guarantees can be made regarding the order of execution. #[proc_macro_attribute] pub fn post_process(_args: TokenStream, input: TokenStream) -> TokenStream { let item: ItemFn = parse_macro_input!(input as ItemFn); diff --git a/plugins/cairo-lang-macro/src/lib.rs b/plugins/cairo-lang-macro/src/lib.rs index 579029ac4..b60440e60 100644 --- a/plugins/cairo-lang-macro/src/lib.rs +++ b/plugins/cairo-lang-macro/src/lib.rs @@ -1,3 +1,20 @@ +//!
+//! +//! **A library for writing Cairo procedural macros in Rust.** +//!
+//! +//! # Cairo procedural macro +//! +//! A Cairo procedural macro is a dynamic library that can be loaded by +//! [Scarb](https://github.com/software-mansion/scarb) package manager during the project build. +//! The goal of procedural macros is to provide dynamic code generation capabilities to Cairo +//! programmers. +//! +//! The code generation should be implemented as a Rust function, that takes [`TokenStream`] as +//! input and returns [`ProcMacroResult`] as output. +//! The function implementing the macro should be wrapped with [`attribute_macro`]. +//! + pub use cairo_lang_macro_attributes::*; #[doc(hidden)] pub use linkme; @@ -11,6 +28,7 @@ mod types; pub use types::*; +#[doc(hidden)] #[derive(Clone)] pub struct ExpansionDefinition { pub name: &'static str, @@ -33,8 +51,8 @@ pub static MACRO_DEFINITIONS_SLICE: [ExpansionDefinition]; /// of the dynamic library re-exporting it. /// /// # Safety -#[no_mangle] #[doc(hidden)] +#[no_mangle] pub unsafe extern "C" fn list_expansions() -> StableExpansionsList { let list = MACRO_DEFINITIONS_SLICE .iter() @@ -49,8 +67,8 @@ pub unsafe extern "C" fn list_expansions() -> StableExpansionsList { /// of the dynamic library re-exporting it. /// /// # Safety -#[no_mangle] #[doc(hidden)] +#[no_mangle] pub unsafe extern "C" fn free_expansions_list(list: StableExpansionsList) { let v = list.into_owned(); v.into_iter().for_each(|v| { @@ -66,8 +84,8 @@ pub unsafe extern "C" fn free_expansions_list(list: StableExpansionsList) { /// The function will be called for each code expansion by the procedural macro. /// /// # Safety -#[no_mangle] #[doc(hidden)] +#[no_mangle] pub unsafe extern "C" fn expand( item_name: *const c_char, stable_token_stream: cairo_lang_macro_stable::StableTokenStream, @@ -101,8 +119,8 @@ pub unsafe extern "C" fn expand( /// by the Rust compiler and the corresponding symbol will be available by the name of the function as id. /// /// # Safety -#[no_mangle] #[doc(hidden)] +#[no_mangle] pub unsafe extern "C" fn free_result(result: StableProcMacroResult) { ProcMacroResult::from_owned_stable(result); } @@ -121,8 +139,8 @@ pub static AUX_DATA_CALLBACKS: [fn(Vec)]; /// behaviour or not. In case no custom behaviour is defined, this is a no-op. /// /// # Safety -#[no_mangle] #[doc(hidden)] +#[no_mangle] pub unsafe extern "C" fn aux_data_callback( stable_aux_data: StableSlice, ) -> StableSlice { diff --git a/plugins/cairo-lang-macro/src/types/conversion.rs b/plugins/cairo-lang-macro/src/types/conversion.rs index 08201f754..7bb6a4838 100644 --- a/plugins/cairo-lang-macro/src/types/conversion.rs +++ b/plugins/cairo-lang-macro/src/types/conversion.rs @@ -242,6 +242,7 @@ impl AuxData { /// Convert to FFI-safe representation. /// /// # Safety + #[doc(hidden)] pub fn maybe_into_stable(aux_data: Option) -> StableAuxData { if let Some(aux_data) = aux_data { aux_data.into_stable() diff --git a/plugins/cairo-lang-macro/src/types/expansions.rs b/plugins/cairo-lang-macro/src/types/expansions.rs index f5fa12c0d..f834c5c81 100644 --- a/plugins/cairo-lang-macro/src/types/expansions.rs +++ b/plugins/cairo-lang-macro/src/types/expansions.rs @@ -2,6 +2,7 @@ use cairo_lang_macro_stable::StableExpansionKind; use std::num::NonZeroU8; /// Representation of a macro expansion kind. +#[doc(hidden)] #[derive(Clone, Debug, Eq, PartialEq)] pub enum ExpansionKind { /// `#[proc_macro_name]` diff --git a/plugins/cairo-lang-macro/src/types/mod.rs b/plugins/cairo-lang-macro/src/types/mod.rs index 8a2e6237b..52c3bc271 100644 --- a/plugins/cairo-lang-macro/src/types/mod.rs +++ b/plugins/cairo-lang-macro/src/types/mod.rs @@ -6,6 +6,7 @@ mod expansions; pub use expansions::*; +/// Result of procedural macro code generation. #[derive(Debug)] pub enum ProcMacroResult { /// Plugin has not taken any action. @@ -20,16 +21,26 @@ pub enum ProcMacroResult { Remove { diagnostics: Vec }, } +/// An abstract stream of Cairo tokens. +/// +/// This is both input and part of an output of a procedural macro. #[derive(Debug, Default, Clone)] pub struct TokenStream { value: String, metadata: TokenStreamMetadata, } +/// Metadata of [`TokenStream`]. +/// +/// This struct can be used to describe the origin of the [`TokenStream`]. #[derive(Debug, Default, Clone)] pub struct TokenStreamMetadata { - original_file_path: Option, - file_id: Option, + /// The path to the file from which the [`TokenStream`] has been created. + pub original_file_path: Option, + /// ID of the file from which the [`TokenStream`] has been created. + /// + /// It is guaranteed, that the `file_id` will be unique for each file. + pub file_id: Option, } impl TokenStream { @@ -47,6 +58,9 @@ impl TokenStream { self } + /// Get `[TokenStreamMetadata`] associated with this [`TokenStream`]. + /// + /// The metadata struct can be used to describe the [`TokenStream`] origin. pub fn metadata(&self) -> &TokenStreamMetadata { &self.metadata } @@ -59,6 +73,7 @@ impl Display for TokenStream { } impl TokenStreamMetadata { + #[doc(hidden)] pub fn new(file_path: impl ToString, file_id: impl ToString) -> Self { Self { original_file_path: Some(file_path.to_string()), @@ -67,11 +82,61 @@ impl TokenStreamMetadata { } } -/// Auxiliary data returned by procedural macro. +/// **Auxiliary data** returned by procedural macro code generation. +/// +/// This struct can be used to collect additional information from the Cairo source code of +/// compiled project. +/// For instance, you can create a procedural macro that collects some information stored by +/// the Cairo programmer as attributes in the project source code. +/// +/// The auxiliary data struct stores `Vec` leaving the serialization and deserialization +/// of the data as user responsibility. No assumptions regarding the serialization algorithm +/// are made. +/// +/// For instance, auxiliary data can be serialized as JSON. +/// +/// ``` +/// use cairo_lang_macro::{AuxData, ProcMacroResult, TokenStream, attribute_macro, post_process_callback} +/// use serde::{Serialize, Deserialize}; +/// #[derive(Debug, Serialize, Deserialize)] +/// struct SomeAuxDataFormat { +/// some_message: String +/// } +/// +/// #[attribute_macro] +/// pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult { +/// let token_stream = TokenStream::new( +/// token_stream.to_string() +/// // Remove macro call to avoid infinite loop. +/// .replace("#[some]", "") +/// ); +/// let value = SomeAuxDataFormat { some_message: "Hello from some macro!".to_string() }; +/// let value = serde_json::to_string(&value).unwrap(); +/// let value: Vec = value.into_bytes(); +/// let aux_data = AuxData::new(value); +/// ProcMacroResult::replace(token_stream, Some(aux_data)) +/// } +/// +/// #[post_process_callback] +/// pub fn callback(aux_data: Vec) { +/// let aux_data = aux_data.into_iter() +/// .map(|aux_data| { +/// let value: Vec = aux_data.into(); +/// let aux_data: SomeAuxDataFormat = serde_json::from_slice(&value).unwrap(); +/// aux_data +/// }) +/// .collect::>(); +/// println!("{:?}", aux_data); +/// } +/// ``` +/// +/// All auxiliary data emitted during compilation can be consumed +/// in the `post_process_callback` implementation. #[derive(Debug, Clone)] pub struct AuxData(Vec); impl AuxData { + /// Create new [`AuxData`] struct from serialized data. pub fn new(data: Vec) -> Self { Self(data) } @@ -92,21 +157,40 @@ impl From for Vec { /// Diagnostic returned by the procedural macro. #[derive(Debug)] pub struct Diagnostic { + /// A human addressed message associated with the [`Diagnostic`]. + /// + /// This message will not be parsed by the compiler, + /// but rather shown to the user as an explanation. pub message: String, + /// The severity of the [`Diagnostic`]. + /// + /// Defines how this diagnostic should influence the compilation. pub severity: Severity, } /// The severity of a diagnostic. +/// +/// This should be roughly equivalent to the severity of Cairo diagnostics. +/// +/// The appropriate action for each diagnostic kind will be taken by `Scarb`. #[derive(Debug)] pub enum Severity { + /// An error has occurred. + /// + /// Emitting diagnostic with [`Severity::Error`] severity will fail the source code compilation. Error = 1, + /// A warning suggestion will be shown to the user. + /// + /// Emitting diagnostic with [`Severity::Warning`] severity does not stop the compilation. Warning = 2, } +/// A set of diagnostics that arose during the computation. #[derive(Debug)] pub struct Diagnostics(Vec); impl Diagnostic { + /// Create new diagnostic with severity [`Severity::Error`]. pub fn error(message: impl ToString) -> Self { Self { message: message.to_string(), @@ -114,6 +198,7 @@ impl Diagnostic { } } + /// Create new diagnostic with severity [`Severity::Warning`]. pub fn warn(message: impl ToString) -> Self { Self { message: message.to_string(), @@ -127,16 +212,22 @@ impl From> for Diagnostics { Self(diagnostics) } } + impl Diagnostics { + /// Create new [`Diagnostics`] from a vector of diagnostics. pub fn new(diagnostics: Vec) -> Self { Self(diagnostics) } + /// Create new diagnostic with severity [`Severity::Error`] + /// and push to the vector. pub fn error(mut self, message: impl ToString) -> Self { self.0.push(Diagnostic::error(message)); self } + /// Create new diagnostic with severity [`Severity::Warning`] + /// and push to the vector. pub fn warn(mut self, message: impl ToString) -> Self { self.0.push(Diagnostic::warn(message)); self @@ -153,18 +244,21 @@ impl IntoIterator for Diagnostics { } impl ProcMacroResult { + /// Create new [`ProcMacroResult::Leave`] variant, empty diagnostics set. pub fn leave() -> Self { Self::Leave { diagnostics: Vec::new(), } } + /// Create new [`ProcMacroResult::Remove`] variant, empty diagnostics set. pub fn remove() -> Self { Self::Remove { diagnostics: Vec::new(), } } + /// Create new [`ProcMacroResult::Replace`] variant, empty diagnostics set. pub fn replace(token_stream: TokenStream, aux_data: Option) -> Self { Self::Replace { aux_data, @@ -173,6 +267,7 @@ impl ProcMacroResult { } } + /// Append diagnostics to the [`ProcMacroResult`] diagnostics set. pub fn with_diagnostics(mut self, diagnostics: Diagnostics) -> Self { match &mut self { Self::Leave { diagnostics: d } => d.extend(diagnostics), diff --git a/scarb/tests/build_cairo_plugin.rs b/scarb/tests/build_cairo_plugin.rs index d289037b1..4a4d96182 100644 --- a/scarb/tests/build_cairo_plugin.rs +++ b/scarb/tests/build_cairo_plugin.rs @@ -484,11 +484,7 @@ fn can_return_aux_data_from_plugin() { let value: Vec = value.into_bytes(); let aux_data = AuxData::new(value); - ProcMacroResult::Replace { - token_stream, - aux_data: Some(aux_data), - diagnostics: Vec::new() - } + ProcMacroResult::replace(token_stream, Some(aux_data)) } #[post_process]