Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite cairo-lang-macro docs #1215

Merged
merged 1 commit into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions plugins/cairo-lang-macro-attributes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
28 changes: 23 additions & 5 deletions plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
//! <br>
//!
//! **A library for writing Cairo procedural macros in Rust.**
//! <br>
//!
//! # 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;
Expand All @@ -11,6 +28,7 @@ mod types;

pub use types::*;

#[doc(hidden)]
#[derive(Clone)]
pub struct ExpansionDefinition {
pub name: &'static str,
Expand All @@ -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()
Expand All @@ -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| {
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
Expand All @@ -121,8 +139,8 @@ pub static AUX_DATA_CALLBACKS: [fn(Vec<AuxData>)];
/// 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<StableAuxData>,
) -> StableSlice<StableAuxData> {
Expand Down
1 change: 1 addition & 0 deletions plugins/cairo-lang-macro/src/types/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ impl AuxData {
/// Convert to FFI-safe representation.
///
/// # Safety
#[doc(hidden)]
pub fn maybe_into_stable(aux_data: Option<Self>) -> StableAuxData {
if let Some(aux_data) = aux_data {
aux_data.into_stable()
Expand Down
1 change: 1 addition & 0 deletions plugins/cairo-lang-macro/src/types/expansions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]`
Expand Down
101 changes: 98 additions & 3 deletions plugins/cairo-lang-macro/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -20,16 +21,26 @@ pub enum ProcMacroResult {
Remove { diagnostics: Vec<Diagnostic> },
}

/// 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<String>,
file_id: Option<String>,
/// The path to the file from which the [`TokenStream`] has been created.
pub original_file_path: Option<String>,
/// 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<String>,
}

impl TokenStream {
Expand All @@ -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
}
Expand All @@ -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()),
Expand All @@ -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<u8>` 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<u8> = 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<AuxData>) {
/// let aux_data = aux_data.into_iter()
/// .map(|aux_data| {
/// let value: Vec<u8> = aux_data.into();
/// let aux_data: SomeAuxDataFormat = serde_json::from_slice(&value).unwrap();
/// aux_data
/// })
/// .collect::<Vec<_>>();
/// 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<u8>);

impl AuxData {
/// Create new [`AuxData`] struct from serialized data.
pub fn new(data: Vec<u8>) -> Self {
Self(data)
}
Expand All @@ -92,28 +157,48 @@ impl From<AuxData> for Vec<u8> {
/// 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<Diagnostic>);

impl Diagnostic {
/// Create new diagnostic with severity [`Severity::Error`].
pub fn error(message: impl ToString) -> Self {
Self {
message: message.to_string(),
severity: Severity::Error,
}
}

/// Create new diagnostic with severity [`Severity::Warning`].
pub fn warn(message: impl ToString) -> Self {
Self {
message: message.to_string(),
Expand All @@ -127,16 +212,22 @@ impl From<Vec<Diagnostic>> for Diagnostics {
Self(diagnostics)
}
}

impl Diagnostics {
/// Create new [`Diagnostics`] from a vector of diagnostics.
pub fn new(diagnostics: Vec<Diagnostic>) -> 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
Expand All @@ -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<AuxData>) -> Self {
Self::Replace {
aux_data,
Expand All @@ -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),
Expand Down
6 changes: 1 addition & 5 deletions scarb/tests/build_cairo_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,11 +484,7 @@ fn can_return_aux_data_from_plugin() {
let value: Vec<u8> = 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]
Expand Down
Loading