diff --git a/plugins/cairo-lang-macro-attributes/src/lib.rs b/plugins/cairo-lang-macro-attributes/src/lib.rs index e39294732..b1ab28db9 100644 --- a/plugins/cairo-lang-macro-attributes/src/lib.rs +++ b/plugins/cairo-lang-macro-attributes/src/lib.rs @@ -34,3 +34,27 @@ pub fn macro_commons(_input: TokenStream) -> TokenStream { } }) } + +/// AuxData callback helper. +/// +/// This macro hides the conversion to stable ABI structs from the user. +/// +/// # Safety +/// Note that AuxData deserialization may fail. +#[proc_macro_attribute] +pub fn aux_data_callback(_args: TokenStream, input: TokenStream) -> TokenStream { + let item: ItemFn = parse_macro_input!(input as ItemFn); + let item_name = &item.sig.ident; + let expanded = quote! { + #item + + #[no_mangle] + pub unsafe extern "C" fn aux_data_callback(aux_data: cairo_lang_macro_stable::ffi::StableSlice) { + let (ptr, n) = aux_data.into_raw_parts(); + let aux_data: &[cairo_lang_macro_stable::StableAuxData] = std::slice::from_raw_parts(ptr, n); + let aux_data = aux_data.iter().filter_map(|a| cairo_lang_macro::AuxData::from_stable(a)).collect::>(); + #item_name(aux_data); + } + }; + TokenStream::from(expanded) +} diff --git a/plugins/cairo-lang-macro/src/lib.rs b/plugins/cairo-lang-macro/src/lib.rs index e50a68db3..5889d1862 100644 --- a/plugins/cairo-lang-macro/src/lib.rs +++ b/plugins/cairo-lang-macro/src/lib.rs @@ -49,6 +49,12 @@ impl AuxData { pub fn try_new(value: T) -> Result { Ok(Self(serde_json::to_string(&value)?)) } + + pub fn from_aux_data( + aux_data: Self, + ) -> Result { + serde_json::from_str(&aux_data.to_string()) + } } impl Display for AuxData { @@ -166,7 +172,7 @@ impl ProcMacroResult { .collect::>(); ProcMacroResult::Replace { token_stream: TokenStream::from_stable(token_stream), - aux_data: AuxData::from_stable(aux_data), + aux_data: AuxData::from_stable(&aux_data), diagnostics, } } @@ -277,10 +283,10 @@ impl AuxData { /// /// # Safety #[doc(hidden)] - pub unsafe fn from_stable(aux_data: StableAuxData) -> Option { + pub unsafe fn from_stable(aux_data: &StableAuxData) -> Option { match aux_data { StableAuxData::None => None, - StableAuxData::Some(raw) => Some(Self::new(from_raw_cstr(raw))), + StableAuxData::Some(raw) => Some(Self::new(from_raw_cstr(*raw))), } } diff --git a/scarb/src/compiler/plugin/proc_macro/ffi.rs b/scarb/src/compiler/plugin/proc_macro/ffi.rs index 309631c65..8f9c89ce7 100644 --- a/scarb/src/compiler/plugin/proc_macro/ffi.rs +++ b/scarb/src/compiler/plugin/proc_macro/ffi.rs @@ -1,8 +1,8 @@ use crate::core::{Config, Package, PackageId}; use anyhow::{Context, Result}; use cairo_lang_defs::patcher::PatchBuilder; -use cairo_lang_macro::{ProcMacroResult, TokenStream}; -use cairo_lang_macro_stable::{StableProcMacroResult, StableTokenStream}; +use cairo_lang_macro::{AuxData, ProcMacroResult, TokenStream}; +use cairo_lang_macro_stable::{StableAuxData, StableProcMacroResult, StableTokenStream}; use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; use camino::Utf8PathBuf; @@ -10,6 +10,8 @@ use libloading::{Library, Symbol}; use std::fmt::Debug; use crate::compiler::plugin::proc_macro::compilation::SharedLibraryProvider; +use crate::compiler::plugin::proc_macro::ProcMacroAuxData; +use cairo_lang_macro_stable::ffi::StableSlice; #[cfg(not(windows))] use libloading::os::unix::Symbol as RawSymbol; #[cfg(windows)] @@ -91,14 +93,32 @@ impl ProcMacroInstance { // Return obtained result. result } + + pub(crate) fn aux_data_callback(&self, aux_data: Vec) { + // Convert to stable aux data. + let aux_data: Vec = aux_data.into_iter().map(Into::into).collect(); + let aux_data = aux_data + .into_iter() + .map(|a| a.into_stable()) + .collect::>(); + // Create stable slice representation from vector. + // Note this needs to be freed manually. + let aux_data = StableSlice::new(aux_data); + // Actual call to FFI interface for aux data callback. + (self.plugin.vtable.aux_data_callback)(aux_data); + // Free the memory allocated by vec. + let _ = aux_data.into_owned(); + } } type ExpandCode = extern "C" fn(StableTokenStream) -> StableProcMacroResult; type FreeResult = extern "C" fn(StableProcMacroResult); +type AuxDataCallback = extern "C" fn(StableSlice); struct VTableV0 { expand: RawSymbol, free_result: RawSymbol, + aux_data_callback: RawSymbol, } impl VTableV0 { @@ -111,9 +131,14 @@ impl VTableV0 { .get(b"free_result\0") .context("failed to load free_result function for procedural macro")?; let free_result = free_result.into_raw(); + let aux_data_callback: Symbol<'_, AuxDataCallback> = library + .get(b"aux_data_callback\0") + .context("failed to load aux_data_callback function for procedural macro")?; + let aux_data_callback = aux_data_callback.into_raw(); Ok(VTableV0 { expand, free_result, + aux_data_callback, }) } } diff --git a/scarb/src/compiler/plugin/proc_macro/host.rs b/scarb/src/compiler/plugin/proc_macro/host.rs index 6e29f8615..033ac099d 100644 --- a/scarb/src/compiler/plugin/proc_macro/host.rs +++ b/scarb/src/compiler/plugin/proc_macro/host.rs @@ -171,7 +171,13 @@ impl ProcMacroHostPlugin { } } } - let _aux_data = data.into_iter().into_group_map_by(|d| d.macro_package_id); + let aux_data = data.into_iter().into_group_map_by(|d| d.macro_package_id); + for instance in self.macros.iter() { + let data = aux_data.get(&instance.package_id()).cloned(); + if let Some(data) = data { + instance.aux_data_callback(data.clone()); + } + } Ok(()) } } diff --git a/scarb/tests/build_cairo_plugin.rs b/scarb/tests/build_cairo_plugin.rs index 2f120e6df..84ffef3c2 100644 --- a/scarb/tests/build_cairo_plugin.rs +++ b/scarb/tests/build_cairo_plugin.rs @@ -84,6 +84,8 @@ fn simple_project_with_code(t: &impl PathChild, code: impl ToString) { [dependencies] cairo-lang-macro = {{ path = {macro_lib_path}}} cairo-lang-macro-stable = {{ path = {macro_stable_lib_path}}} + serde = "*" + serde_json = "*" "#}, ) .build(t); @@ -91,7 +93,7 @@ fn simple_project_with_code(t: &impl PathChild, code: impl ToString) { fn simple_project(t: &impl PathChild) { let code = indoc! {r#" - use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons}; + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons, AuxData, aux_data_callback}; macro_commons!(); @@ -100,6 +102,9 @@ fn simple_project(t: &impl PathChild) { let _code = token_stream.to_string(); ProcMacroResult::Leave { diagnostics: Vec::new() } } + + #[aux_data_callback] + pub fn callback(_: Vec) {} "#}; simple_project_with_code(t, code); } @@ -268,7 +273,7 @@ fn can_emit_plugin_warning() { simple_project_with_code( &t, indoc! {r#" - use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons, Diagnostic}; + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons, aux_data_callback, AuxData, Diagnostic}; macro_commons!(); @@ -278,6 +283,9 @@ fn can_emit_plugin_warning() { let diag = Diagnostic::warn("Some warning from macro."); ProcMacroResult::Leave { diagnostics: vec![diag] } } + + #[aux_data_callback] + pub fn callback(_: Vec) {} "#}, ); let project = temp.child("hello"); @@ -317,7 +325,7 @@ fn can_emit_plugin_error() { simple_project_with_code( &t, indoc! {r#" - use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons, Diagnostic}; + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons, aux_data_callback, AuxData, Diagnostic}; macro_commons!(); @@ -327,6 +335,9 @@ fn can_emit_plugin_error() { let diag = Diagnostic::error("Some error from macro."); ProcMacroResult::Leave { diagnostics: vec![diag] } } + + #[aux_data_callback] + pub fn callback(_: Vec) {} "#}, ); let project = temp.child("hello"); @@ -366,7 +377,7 @@ fn can_remove_original_node() { simple_project_with_code( &t, indoc! {r#" - use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons}; + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons, AuxData, aux_data_callback}; macro_commons!(); @@ -374,6 +385,9 @@ fn can_remove_original_node() { pub fn some_macro(_: TokenStream) -> ProcMacroResult { ProcMacroResult::Remove { diagnostics: Vec::new() } } + + #[aux_data_callback] + pub fn callback(_: Vec) {} "#}, ); let project = temp.child("hello"); @@ -415,7 +429,7 @@ fn can_replace_original_node() { simple_project_with_code( &t, indoc! {r##" - use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons}; + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons, AuxData, aux_data_callback}; macro_commons!(); @@ -434,6 +448,9 @@ fn can_replace_original_node() { diagnostics: Vec::new() } } + + #[aux_data_callback] + pub fn callback(_: Vec) {} "##}, ); let project = temp.child("hello"); @@ -462,3 +479,79 @@ fn can_replace_original_node() { Run completed successfully, returning [34] "#}); } + +#[test] +fn can_return_aux_data_from_plugin() { + let temp = TempDir::new().unwrap(); + let t = temp.child("some"); + simple_project_with_code( + &t, + indoc! {r##" + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, macro_commons, AuxData, aux_data_callback}; + use serde::{Serialize, Deserialize}; + + macro_commons!(); + + #[derive(Debug, Serialize, Deserialize)] + struct SomeMacroDataFormat { + msg: 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]", "") + .replace("12", "34") + ); + let aux_data = AuxData::try_new( + SomeMacroDataFormat { msg: "Hello from some macro!".to_string() } + ).unwrap(); + ProcMacroResult::Replace { + token_stream, + aux_data: Some(aux_data), + diagnostics: Vec::new() + } + } + + #[aux_data_callback] + pub fn callback(aux_data: Vec) { + let aux_data = aux_data.into_iter() + .map(AuxData::from_aux_data::) + .collect::, serde_json::Error>>(); + println!("{:?}", aux_data); + } + + "##}, + ); + + let project = temp.child("hello"); + ProjectBuilder::start() + .name("hello") + .version("1.0.0") + .dep_starknet() + .dep("some", &t) + .lib_cairo(indoc! {r#" + #[some] + fn main() -> felt252 { 12 } + "#}) + .build(&project); + + Scarb::quick_snapbox() + .arg("cairo-run") + // Disable output from Cargo. + .env("CARGO_TERM_QUIET", "true") + .current_dir(&project) + .assert() + .success() + .stdout_matches(indoc! {r#" + [..]Compiling some v1.0.0 ([..]Scarb.toml) + [..]Compiling hello v1.0.0 ([..]Scarb.toml) + Ok([SomeMacroDataFormat { msg: "Hello from some macro!" }]) + [..]Finished release target(s) in [..] + [..]Running hello + [..]Run completed successfully, returning [..] + "#}); +}