diff --git a/plugins/cairo-lang-macro-attributes/src/lib.rs b/plugins/cairo-lang-macro-attributes/src/lib.rs index 3457f9c4f..ae74d6c00 100644 --- a/plugins/cairo-lang-macro-attributes/src/lib.rs +++ b/plugins/cairo-lang-macro-attributes/src/lib.rs @@ -11,7 +11,11 @@ use syn::{parse_macro_input, ItemFn}; /// 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 { - macro_helper(input, quote!(::cairo_lang_macro::ExpansionKind::Attr)) + macro_helper( + input, + quote!(::cairo_lang_macro::ExpansionKind::Attr), + quote!(::cairo_lang_macro::ExpansionFunc::Attr), + ) } /// Constructs the inline macro implementation. @@ -21,7 +25,11 @@ pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream { /// Note, that this macro can be used multiple times, to define multiple independent attribute macros. #[proc_macro_attribute] pub fn inline_macro(_args: TokenStream, input: TokenStream) -> TokenStream { - macro_helper(input, quote!(::cairo_lang_macro::ExpansionKind::Inline)) + macro_helper( + input, + quote!(::cairo_lang_macro::ExpansionKind::Inline), + quote!(::cairo_lang_macro::ExpansionFunc::Other), + ) } /// Constructs the derive macro implementation. @@ -31,10 +39,14 @@ pub fn inline_macro(_args: TokenStream, input: TokenStream) -> TokenStream { /// Note, that this macro can be used multiple times, to define multiple independent attribute macros. #[proc_macro_attribute] pub fn derive_macro(_args: TokenStream, input: TokenStream) -> TokenStream { - macro_helper(input, quote!(::cairo_lang_macro::ExpansionKind::Derive)) + macro_helper( + input, + quote!(::cairo_lang_macro::ExpansionKind::Derive), + quote!(::cairo_lang_macro::ExpansionFunc::Other), + ) } -fn macro_helper(input: TokenStream, kind: impl ToTokens) -> TokenStream { +fn macro_helper(input: TokenStream, kind: impl ToTokens, func: impl ToTokens) -> TokenStream { let item: ItemFn = parse_macro_input!(input as ItemFn); let original_item_name = item.sig.ident.to_string(); let item = hide_name(item); @@ -56,7 +68,7 @@ fn macro_helper(input: TokenStream, kind: impl ToTokens) -> TokenStream { ::cairo_lang_macro::ExpansionDefinition{ name: #original_item_name, kind: #kind, - fun: #item_name, + fun: #func(#item_name), }; }; TokenStream::from(expanded) diff --git a/plugins/cairo-lang-macro-stable/src/lib.rs b/plugins/cairo-lang-macro-stable/src/lib.rs index 654e8169d..dd91b21a8 100644 --- a/plugins/cairo-lang-macro-stable/src/lib.rs +++ b/plugins/cairo-lang-macro-stable/src/lib.rs @@ -72,6 +72,7 @@ pub struct StableProcMacroResult { #[repr(C)] pub struct StableResultWrapper { pub input: StableTokenStream, + pub input_attr: StableTokenStream, pub output: StableProcMacroResult, } diff --git a/plugins/cairo-lang-macro/src/lib.rs b/plugins/cairo-lang-macro/src/lib.rs index 3918953c4..00ca4f01e 100644 --- a/plugins/cairo-lang-macro/src/lib.rs +++ b/plugins/cairo-lang-macro/src/lib.rs @@ -37,7 +37,11 @@ pub struct ExpansionDefinition { pub fun: ExpansionFunc, } -type ExpansionFunc = fn(TokenStream) -> ProcMacroResult; +#[derive(Clone)] +pub enum ExpansionFunc { + Attr(fn(TokenStream, TokenStream) -> ProcMacroResult), + Other(fn(TokenStream) -> ProcMacroResult), +} /// Distributed slice for storing procedural macro code expansion capabilities. /// @@ -89,24 +93,30 @@ pub unsafe extern "C" fn free_expansions_list(list: StableExpansionsList) { #[no_mangle] pub unsafe extern "C" fn expand( item_name: *const c_char, + stable_attr: cairo_lang_macro_stable::StableTokenStream, stable_token_stream: cairo_lang_macro_stable::StableTokenStream, ) -> cairo_lang_macro_stable::StableResultWrapper { let token_stream = TokenStream::from_stable(&stable_token_stream); + let attr_token_stream = TokenStream::from_stable(&stable_attr); let item_name = CStr::from_ptr(item_name).to_string_lossy().to_string(); let fun = MACRO_DEFINITIONS_SLICE .iter() .find_map(|m| { if m.name == item_name.as_str() { - Some(m.fun) + Some(m.fun.clone()) } else { None } }) .expect("procedural macro not found"); - let result = fun(token_stream); + let result = match fun { + ExpansionFunc::Attr(fun) => fun(attr_token_stream, token_stream), + ExpansionFunc::Other(fun) => fun(token_stream), + }; let result: StableProcMacroResult = result.into_stable(); cairo_lang_macro_stable::StableResultWrapper { input: stable_token_stream, + input_attr: stable_attr, output: result, } } diff --git a/plugins/cairo-lang-macro/src/types/mod.rs b/plugins/cairo-lang-macro/src/types/mod.rs index e1124cdd5..66bac47c5 100644 --- a/plugins/cairo-lang-macro/src/types/mod.rs +++ b/plugins/cairo-lang-macro/src/types/mod.rs @@ -107,7 +107,7 @@ impl TokenStreamMetadata { /// } /// /// #[attribute_macro] -/// pub fn some_macro(token_stream: TokenStream) -> ProcMacroResult { +/// pub fn some_macro(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { /// let token_stream = TokenStream::new( /// token_stream.to_string() /// // Remove macro call to avoid infinite loop. diff --git a/scarb/src/compiler/plugin/proc_macro/ffi.rs b/scarb/src/compiler/plugin/proc_macro/ffi.rs index 5256ad187..9ee4ca3ea 100644 --- a/scarb/src/compiler/plugin/proc_macro/ffi.rs +++ b/scarb/src/compiler/plugin/proc_macro/ffi.rs @@ -127,21 +127,25 @@ impl ProcMacroInstance { pub(crate) fn generate_code( &self, item_name: SmolStr, + attr: TokenStream, token_stream: TokenStream, ) -> ProcMacroResult { // This must be manually freed with call to from_owned_stable. let stable_token_stream = token_stream.into_stable(); + let stable_attr = attr.into_stable(); // Allocate proc macro name. let item_name = CString::new(item_name.to_string()).unwrap().into_raw(); // Call FFI interface for code expansion. // Note that `stable_result` has been allocated by the dynamic library. - let stable_result = (self.plugin.vtable.expand)(item_name, stable_token_stream); + let stable_result = + (self.plugin.vtable.expand)(item_name, stable_attr, stable_token_stream); // Free proc macro name. let _ = unsafe { CString::from_raw(item_name) }; // Free the memory allocated by the `stable_token_stream`. // This will call `CString::from_raw` under the hood, to take ownership. unsafe { TokenStream::from_owned_stable(stable_result.input); + TokenStream::from_owned_stable(stable_result.input_attr); }; // Create Rust representation of the result. // Note, that the memory still needs to be freed on the allocator side! @@ -201,7 +205,8 @@ impl Expansion { type ListExpansions = extern "C" fn() -> StableExpansionsList; type FreeExpansionsList = extern "C" fn(StableExpansionsList); -type ExpandCode = extern "C" fn(*const c_char, StableTokenStream) -> StableResultWrapper; +type ExpandCode = + extern "C" fn(*const c_char, StableTokenStream, StableTokenStream) -> StableResultWrapper; type FreeResult = extern "C" fn(StableProcMacroResult); type PostProcessCallback = extern "C" fn(StablePostProcessContext) -> StablePostProcessContext; diff --git a/scarb/src/compiler/plugin/proc_macro/host.rs b/scarb/src/compiler/plugin/proc_macro/host.rs index 74e6d9117..ad9dd2ce3 100644 --- a/scarb/src/compiler/plugin/proc_macro/host.rs +++ b/scarb/src/compiler/plugin/proc_macro/host.rs @@ -157,7 +157,7 @@ impl ProcMacroHostPlugin { &self, db: &dyn SyntaxGroup, item_ast: ast::ModuleItem, - ) -> (Option, TokenStream) { + ) -> (Option<(ProcMacroId, TokenStream)>, TokenStream) { let mut item_builder = PatchBuilder::new(db); let input = match item_ast { ast::ModuleItem::Struct(struct_ast) => { @@ -223,7 +223,7 @@ impl ProcMacroHostPlugin { db: &dyn SyntaxGroup, builder: &mut PatchBuilder<'_>, attrs: Vec, - ) -> Option { + ) -> Option<(ProcMacroId, TokenStream)> { let mut expansion = None; for attr in attrs { if expansion.is_none() { @@ -232,8 +232,11 @@ impl ProcMacroHostPlugin { structured_attr.id.clone(), ExpansionKind::Attr, )); - if found.is_some() { - expansion = found; + if let Some(found) = found { + let mut args_builder = PatchBuilder::new(db); + args_builder.add_node(attr.arguments(db).as_syntax_node()); + let args = TokenStream::new(args_builder.code); + expansion = Some((found, args)); // Do not add the attribute for found expansion. continue; } @@ -300,9 +303,11 @@ impl ProcMacroHostPlugin { let mut derived_code = PatchBuilder::new(db); for derive in derives { - let result = self - .instance(derive.package_id) - .generate_code(derive.expansion.name.clone(), token_stream.clone()); + let result = self.instance(derive.package_id).generate_code( + derive.expansion.name.clone(), + TokenStream::empty(), + token_stream.clone(), + ); // Register diagnostics. all_diagnostics.extend(result.diagnostics); @@ -353,12 +358,15 @@ impl ProcMacroHostPlugin { fn expand_attribute( &self, input: ProcMacroId, + args: TokenStream, token_stream: TokenStream, stable_ptr: SyntaxStablePtrId, ) -> PluginResult { - let result = self - .instance(input.package_id) - .generate_code(input.expansion.name.clone(), token_stream.clone()); + let result = self.instance(input.package_id).generate_code( + input.expansion.name.clone(), + args.clone(), + token_stream.clone(), + ); // Handle token stream. if result.token_stream.is_empty() { @@ -574,10 +582,10 @@ impl MacroPlugin for ProcMacroHostPlugin { // Expand first attribute. // Note that we only expand the first attribute, as we assume that the rest of the attributes // will be handled by a subsequent call to this function. - if let (Some(input), token_stream) = self.parse_attribute(db, item_ast.clone()) { + if let (Some((input, args)), token_stream) = self.parse_attribute(db, item_ast.clone()) { let token_stream = token_stream.with_metadata(stream_metadata.clone()); let stable_ptr = item_ast.clone().stable_ptr().untyped(); - return self.expand_attribute(input, token_stream, stable_ptr); + return self.expand_attribute(input, args, token_stream, stable_ptr); } // Expand all derives. @@ -640,9 +648,11 @@ impl InlineMacroExprPlugin for ProcMacroInlinePlugin { let stable_ptr = syntax.clone().stable_ptr().untyped(); let token_stream = TokenStream::from_syntax_node(db, syntax.as_syntax_node()); - let result = self - .instance() - .generate_code(self.expansion.name.clone(), token_stream); + let result = self.instance().generate_code( + self.expansion.name.clone(), + TokenStream::empty(), + token_stream, + ); // Handle diagnostics. let diagnostics = into_cairo_diagnostics(result.diagnostics, stable_ptr); let token_stream = result.token_stream.clone(); diff --git a/scarb/tests/build_cairo_plugin.rs b/scarb/tests/build_cairo_plugin.rs index 5bac4e43b..9a55a8e84 100644 --- a/scarb/tests/build_cairo_plugin.rs +++ b/scarb/tests/build_cairo_plugin.rs @@ -102,10 +102,10 @@ impl Default for CairoPluginProjectBuilder { fn default() -> Self { let default_name = "some"; let default_code = indoc! {r#" - use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, AuxData}; + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn some(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { ProcMacroResult::new(token_stream) } "#}; @@ -286,7 +286,7 @@ fn can_emit_plugin_warning() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, Diagnostic}; #[attribute_macro] - pub fn some(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let diag = Diagnostic::warn("Some warning from macro."); ProcMacroResult::new(token_stream) .with_diagnostics(vec![diag].into()) @@ -332,7 +332,7 @@ fn can_emit_plugin_error() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, Diagnostic}; #[attribute_macro] - pub fn some(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let diag = Diagnostic::error("Some error from macro."); ProcMacroResult::new(token_stream) .with_diagnostics(vec![diag].into()) @@ -378,7 +378,7 @@ fn can_remove_original_node() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn some(_: TokenStream) -> ProcMacroResult { + pub fn some(_attr: TokenStream, _: TokenStream) -> ProcMacroResult { ProcMacroResult::new(TokenStream::empty()) } "#}) @@ -424,7 +424,7 @@ fn can_replace_original_node() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn some(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let token_stream = TokenStream::new( token_stream .to_string() @@ -478,7 +478,7 @@ fn can_return_aux_data_from_plugin() { } #[attribute_macro] - pub fn some(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let token_stream = TokenStream::new( token_stream .to_string() @@ -549,7 +549,7 @@ fn can_read_token_stream_metadata() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn some(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { println!("{:#?}", token_stream.metadata()); ProcMacroResult::new(token_stream) } @@ -599,7 +599,7 @@ fn can_define_multiple_macros() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, AuxData, PostProcessContext, post_process}; #[attribute_macro] - pub fn hello(token_stream: TokenStream) -> ProcMacroResult { + pub fn hello(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let token_stream = TokenStream::new( token_stream .to_string() @@ -612,7 +612,7 @@ fn can_define_multiple_macros() { } #[attribute_macro] - pub fn world(token_stream: TokenStream) -> ProcMacroResult { + pub fn world(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let token_stream = TokenStream::new( token_stream .to_string() @@ -638,7 +638,7 @@ fn can_define_multiple_macros() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, AuxData, PostProcessContext, post_process}; #[attribute_macro] - pub fn beautiful(token_stream: TokenStream) -> ProcMacroResult { + pub fn beautiful(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let token_stream = TokenStream::new( token_stream .to_string() @@ -698,12 +698,12 @@ fn cannot_duplicate_macros() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn hello(token_stream: TokenStream) -> ProcMacroResult { + pub fn hello(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { ProcMacroResult::new(token_stream) } #[attribute_macro] - pub fn hello(token_stream: TokenStream) -> ProcMacroResult { + pub fn hello(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { ProcMacroResult::new(token_stream) } "##}) @@ -738,12 +738,12 @@ fn cannot_duplicate_macros_across_packages() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn hello(token_stream: TokenStream) -> ProcMacroResult { + pub fn hello(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { ProcMacroResult::new(token_stream) } #[attribute_macro] - pub fn world(token_stream: TokenStream) -> ProcMacroResult { + pub fn world(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { ProcMacroResult::new(token_stream) } "#}) @@ -756,7 +756,7 @@ fn cannot_duplicate_macros_across_packages() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn hello(token_stream: TokenStream) -> ProcMacroResult { + pub fn hello(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { ProcMacroResult::new(token_stream) } "#}) @@ -800,7 +800,7 @@ fn cannot_use_undefined_macro() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; #[attribute_macro] - pub fn hello(token_stream: TokenStream) -> ProcMacroResult { + pub fn hello(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { ProcMacroResult::new(token_stream) } "##}) @@ -844,7 +844,7 @@ fn can_resolve_full_path_markers() { use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro, post_process, PostProcessContext}; #[attribute_macro] - pub fn some(token_stream: TokenStream) -> ProcMacroResult { + pub fn some(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let full_path_markers = vec!["some-key".to_string()]; let code = format!( @@ -1058,7 +1058,7 @@ fn can_use_both_derive_and_attr() { use cairo_lang_macro::{derive_macro, attribute_macro, ProcMacroResult, TokenStream}; #[attribute_macro] - pub fn first_attribute(token_stream: TokenStream) -> ProcMacroResult { + pub fn first_attribute(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { ProcMacroResult::new(TokenStream::new( token_stream.to_string() .replace("SomeType", "OtherType") @@ -1066,7 +1066,7 @@ fn can_use_both_derive_and_attr() { } #[attribute_macro] - pub fn second_attribute(token_stream: TokenStream) -> ProcMacroResult { + pub fn second_attribute(_attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { let token_stream = TokenStream::new( token_stream.to_string().replace("OtherType", "RenamedStruct") ); @@ -1128,3 +1128,52 @@ fn can_use_both_derive_and_attr() { Run completed successfully, returning [32] "#}); } + +#[test] +fn can_read_attribute_args() { + let temp = TempDir::new().unwrap(); + let t = temp.child("some"); + CairoPluginProjectBuilder::default() + .lib_rs(indoc! {r##" + use cairo_lang_macro::{ProcMacroResult, TokenStream, attribute_macro}; + + #[attribute_macro] + pub fn some(attr: TokenStream, token_stream: TokenStream) -> ProcMacroResult { + println!("{}", attr); + ProcMacroResult::new(token_stream) + } + "##}) + .build(&t); + + let project = temp.child("hello"); + ProjectBuilder::start() + .name("hello") + .version("1.0.0") + .dep_starknet() + .dep("some", &t) + .lib_cairo(indoc! {r#" + #[some( + first: "aaa", + second: "bbb", + )] + fn main() -> felt252 { 12 } + "#}) + .build(&project); + + Scarb::quick_snapbox() + .arg("build") + // 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) + ( + first: "aaa", + second: "bbb", + ) + [..]Finished release target(s) in [..] + "#}); +}