Skip to content

Commit

Permalink
Implement aux_data callback
Browse files Browse the repository at this point in the history
commit-id:5a9c140b
  • Loading branch information
maciektr committed Mar 13, 2024
1 parent 941f241 commit 7840edd
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 8 deletions.
25 changes: 25 additions & 0 deletions plugins/cairo-lang-macro-attributes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,28 @@ pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream {
};
TokenStream::from(expanded)
}

/// 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(stable_aux_data: cairo_lang_macro_stable::ffi::StableSlice<cairo_lang_macro_stable::StableAuxData>) -> cairo_lang_macro_stable::ffi::StableSlice<cairo_lang_macro_stable::StableAuxData> {
let (ptr, n) = stable_aux_data.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::<Vec<_>>();
#item_name(aux_data);
stable_aux_data
}
};
TokenStream::from(expanded)
}
6 changes: 6 additions & 0 deletions plugins/cairo-lang-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ impl AuxData {
pub fn try_new<T: serde::Serialize>(value: T) -> Result<Self, serde_json::Error> {
Ok(Self(serde_json::to_string(&value)?))
}

pub fn from_aux_data<T: serde::de::DeserializeOwned>(
aux_data: Self,
) -> Result<T, serde_json::Error> {
serde_json::from_str(&aux_data.to_string())
}
}

impl Display for AuxData {
Expand Down
31 changes: 29 additions & 2 deletions scarb/src/compiler/plugin/proc_macro/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
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, StableResultWrapper, StableTokenStream};
use cairo_lang_macro::{AuxData, ProcMacroResult, TokenStream};
use cairo_lang_macro_stable::{
StableAuxData, StableProcMacroResult, StableResultWrapper, StableTokenStream,
};
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::{ast, TypedSyntaxNode};
use camino::Utf8PathBuf;
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)]
Expand Down Expand Up @@ -91,14 +95,32 @@ impl ProcMacroInstance {
// Return obtained result.
result
}

pub(crate) fn aux_data_callback(&self, aux_data: Vec<ProcMacroAuxData>) {
// Convert to stable aux data.
let aux_data: Vec<AuxData> = aux_data.into_iter().map(Into::into).collect();
let aux_data = aux_data
.into_iter()
.map(|a| a.into_stable())
.collect::<Vec<_>>();
// 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.
let aux_data = (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) -> StableResultWrapper;
type FreeResult = extern "C" fn(StableProcMacroResult);
type AuxDataCallback = extern "C" fn(StableSlice<StableAuxData>) -> StableSlice<StableAuxData>;

struct VTableV0 {
expand: RawSymbol<ExpandCode>,
free_result: RawSymbol<FreeResult>,
aux_data_callback: RawSymbol<AuxDataCallback>,
}

impl VTableV0 {
Expand All @@ -111,9 +133,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,
})
}
}
Expand Down
8 changes: 7 additions & 1 deletion scarb/src/compiler/plugin/proc_macro/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
Expand Down
103 changes: 98 additions & 5 deletions scarb/tests/build_cairo_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,25 @@ 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);
}

fn simple_project(t: &impl PathChild) {
let code = indoc! {r#"
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro};
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, AuxData, aux_data_callback};
#[attribute_macro]
pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult {
let _code = token_stream.to_string();
ProcMacroResult::Leave { diagnostics: Vec::new() }
}
#[aux_data_callback]
pub fn callback(_: Vec<AuxData>) {}
"#};
simple_project_with_code(t, code);
}
Expand Down Expand Up @@ -266,14 +271,17 @@ fn can_emit_plugin_warning() {
simple_project_with_code(
&t,
indoc! {r#"
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, Diagnostic};
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, aux_data_callback, AuxData, Diagnostic};
#[attribute_macro]
pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult {
let _code = token_stream.to_string();
let diag = Diagnostic::warn("Some warning from macro.");
ProcMacroResult::Leave { diagnostics: vec![diag] }
}
#[aux_data_callback]
pub fn callback(_: Vec<AuxData>) {}
"#},
);
let project = temp.child("hello");
Expand Down Expand Up @@ -313,14 +321,17 @@ fn can_emit_plugin_error() {
simple_project_with_code(
&t,
indoc! {r#"
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, Diagnostic};
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, aux_data_callback, AuxData, Diagnostic};
#[attribute_macro]
pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult {
let _code = token_stream.to_string();
let diag = Diagnostic::error("Some error from macro.");
ProcMacroResult::Leave { diagnostics: vec![diag] }
}
#[aux_data_callback]
pub fn callback(_: Vec<AuxData>) {}
"#},
);
let project = temp.child("hello");
Expand Down Expand Up @@ -360,12 +371,15 @@ fn can_remove_original_node() {
simple_project_with_code(
&t,
indoc! {r#"
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro};
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, AuxData, aux_data_callback};
#[attribute_macro]
pub fn some_macro(_: TokenStream) -> ProcMacroResult {
ProcMacroResult::Remove { diagnostics: Vec::new() }
}
#[aux_data_callback]
pub fn callback(_: Vec<AuxData>) {}
"#},
);
let project = temp.child("hello");
Expand Down Expand Up @@ -407,7 +421,7 @@ fn can_replace_original_node() {
simple_project_with_code(
&t,
indoc! {r##"
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro};
use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, AuxData, aux_data_callback};
#[attribute_macro]
pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult {
Expand All @@ -424,6 +438,9 @@ fn can_replace_original_node() {
diagnostics: Vec::new()
}
}
#[aux_data_callback]
pub fn callback(_: Vec<AuxData>) {}
"##},
);
let project = temp.child("hello");
Expand Down Expand Up @@ -452,3 +469,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<AuxData>) {
let aux_data = aux_data.into_iter()
.map(AuxData::from_aux_data::<SomeMacroDataFormat>)
.collect::<Result<Vec<_>, 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 [..]
"#});
}

0 comments on commit 7840edd

Please sign in to comment.