From 7c08cb80c1c67c61197b36977a2b41ad2f3499b7 Mon Sep 17 00:00:00 2001 From: maciektr Date: Thu, 28 Mar 2024 15:19:32 +0100 Subject: [PATCH] Implement inline macros expansion commit-id:b7466c6a --- .../cairo-lang-macro-attributes/src/lib.rs | 40 ++++++ plugins/cairo-lang-macro/src/types/mod.rs | 58 ++++++++ scarb/src/compiler/plugin/proc_macro/ffi.rs | 15 +- scarb/src/compiler/plugin/proc_macro/host.rs | 128 +++++++++++++++--- 4 files changed, 218 insertions(+), 23 deletions(-) diff --git a/plugins/cairo-lang-macro-attributes/src/lib.rs b/plugins/cairo-lang-macro-attributes/src/lib.rs index 2e1b92730..ddfecdb51 100644 --- a/plugins/cairo-lang-macro-attributes/src/lib.rs +++ b/plugins/cairo-lang-macro-attributes/src/lib.rs @@ -38,6 +38,46 @@ pub fn attribute_macro(_args: TokenStream, input: TokenStream) -> TokenStream { TokenStream::from(expanded) } +#[proc_macro_attribute] +pub fn inline_macro(_args: TokenStream, input: TokenStream) -> TokenStream { + let item: ItemFn = parse_macro_input!(input as ItemFn); + let original_item_name = item.sig.ident.to_string(); + let item = hide_name(item); + let item_name = &item.sig.ident; + + let callback_link = format!( + "EXPANSIONS_DESERIALIZE_{}", + item_name.to_string().to_uppercase() + ); + let callback_link = syn::Ident::new(callback_link.as_str(), item.span()); + + // We wrap original function with `InlineProcMacroResult` to `ProcMacroResult` conversion. + // This stems from the fact, that `ProcMacroResult::Remove` does not really make sense for inline macros. + let wrapper_name = format!("{item_name}_inline_wrapper"); + let wrapper_name = syn::Ident::new(wrapper_name.as_str(), item.span()); + + let expanded = quote! { + #item + + fn #wrapper_name(token_stream: ::cairo_lang_macro::TokenStream) -> ::cairo_lang_macro::ProcMacroResult { + // Assign function pointer, to validate type. + let f: fn(::cairo_lang_macro::TokenStream) -> ::cairo_lang_macro::InlineProcMacroResult = #item_name; + f(token_stream).into() + } + + #[::cairo_lang_macro::linkme::distributed_slice(::cairo_lang_macro::MACRO_DEFINITIONS_SLICE)] + #[linkme(crate = ::cairo_lang_macro::linkme)] + static #callback_link: ::cairo_lang_macro::ExpansionDefinition = + ::cairo_lang_macro::ExpansionDefinition{ + name: #original_item_name, + kind: ::cairo_lang_macro::ExpansionKind::Inline, + fun: #wrapper_name, + }; + }; + + TokenStream::from(expanded) +} + /// Constructs the post-processing callback. /// /// This callback will be called after the source code compilation (and thus after all the procedural diff --git a/plugins/cairo-lang-macro/src/types/mod.rs b/plugins/cairo-lang-macro/src/types/mod.rs index f0812af44..b51e663ec 100644 --- a/plugins/cairo-lang-macro/src/types/mod.rs +++ b/plugins/cairo-lang-macro/src/types/mod.rs @@ -21,6 +21,20 @@ pub enum ProcMacroResult { Remove { diagnostics: Vec }, } +/// Result of inline procedural macro code generation. +/// +/// This enum differs from `ProcMacroResult` by not having `Remove` variant. +pub enum InlineProcMacroResult { + /// Plugin has not taken any action. + Leave { diagnostics: Vec }, + /// Plugin generated [`TokenStream`] replacement. + Replace { + token_stream: TokenStream, + aux_data: Option, + diagnostics: Vec, + }, +} + /// An abstract stream of Cairo tokens. /// /// This is both input and part of an output of a procedural macro. @@ -278,6 +292,50 @@ impl ProcMacroResult { } } +impl InlineProcMacroResult { + /// Create new [`InlineProcMacroResult::Leave`] variant, empty diagnostics set. + pub fn leave() -> Self { + Self::Leave { + diagnostics: Vec::new(), + } + } + + /// Create new [`InlineProcMacroResult::Replace`] variant, empty diagnostics set. + pub fn replace(token_stream: TokenStream, aux_data: Option) -> Self { + Self::Replace { + aux_data, + token_stream, + diagnostics: Vec::new(), + } + } + + /// Append diagnostics to the [`InlineProcMacroResult`] diagnostics set. + pub fn with_diagnostics(mut self, diagnostics: Diagnostics) -> Self { + match &mut self { + Self::Leave { diagnostics: d } => d.extend(diagnostics), + Self::Replace { diagnostics: d, .. } => d.extend(diagnostics), + }; + self + } +} + +impl From for ProcMacroResult { + fn from(result: InlineProcMacroResult) -> Self { + match result { + InlineProcMacroResult::Leave { diagnostics } => ProcMacroResult::Leave { diagnostics }, + InlineProcMacroResult::Replace { + token_stream, + aux_data, + diagnostics, + } => ProcMacroResult::Replace { + token_stream, + aux_data, + diagnostics, + }, + } + } +} + #[cfg(test)] mod tests { use crate::types::TokenStream; diff --git a/scarb/src/compiler/plugin/proc_macro/ffi.rs b/scarb/src/compiler/plugin/proc_macro/ffi.rs index 6e1634f40..afad26f30 100644 --- a/scarb/src/compiler/plugin/proc_macro/ffi.rs +++ b/scarb/src/compiler/plugin/proc_macro/ffi.rs @@ -7,7 +7,7 @@ use cairo_lang_macro_stable::{ StableResultWrapper, StableTokenStream, }; use cairo_lang_syntax::node::db::SyntaxGroup; -use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; +use cairo_lang_syntax::node::{ast, SyntaxNode, TypedSyntaxNode}; use camino::Utf8PathBuf; use libloading::{Library, Symbol}; use std::ffi::{c_char, CStr, CString}; @@ -23,16 +23,21 @@ use libloading::os::unix::Symbol as RawSymbol; use libloading::os::windows::Symbol as RawSymbol; use smol_str::SmolStr; -pub trait FromItemAst { +pub trait FromSyntaxNode { + fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self; fn from_item_ast(db: &dyn SyntaxGroup, item_ast: ast::ModuleItem) -> Self; } -impl FromItemAst for TokenStream { - fn from_item_ast(db: &dyn SyntaxGroup, item_ast: ast::ModuleItem) -> Self { +impl FromSyntaxNode for TokenStream { + fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self { let mut builder = PatchBuilder::new(db); - builder.add_node(item_ast.as_syntax_node()); + builder.add_node(node); Self::new(builder.code) } + + fn from_item_ast(db: &dyn SyntaxGroup, item_ast: ast::ModuleItem) -> Self { + Self::from_syntax_node(db, item_ast.as_syntax_node()) + } } /// Representation of a single procedural macro. diff --git a/scarb/src/compiler/plugin/proc_macro/host.rs b/scarb/src/compiler/plugin/proc_macro/host.rs index 3eed010d8..f7fdab741 100644 --- a/scarb/src/compiler/plugin/proc_macro/host.rs +++ b/scarb/src/compiler/plugin/proc_macro/host.rs @@ -1,12 +1,12 @@ -use crate::compiler::plugin::proc_macro::{Expansion, FromItemAst, ProcMacroInstance}; +use crate::compiler::plugin::proc_macro::{Expansion, FromSyntaxNode, ProcMacroInstance}; use crate::core::{Config, Package, PackageId}; use anyhow::{ensure, Result}; use cairo_lang_defs::db::DefsGroup; -use cairo_lang_defs::plugin::PluginDiagnostic; use cairo_lang_defs::plugin::{ DynGeneratedFileAuxData, GeneratedFileAuxData, MacroPlugin, MacroPluginMetadata, PluginGeneratedFile, PluginResult, }; +use cairo_lang_defs::plugin::{InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic}; use cairo_lang_macro::{ AuxData, Diagnostic, ExpansionKind, ProcMacroResult, Severity, TokenStream, TokenStreamMetadata, }; @@ -129,12 +129,6 @@ impl ProcMacroHostPlugin { Ok(Self { macros }) } - /// Handle `proc_macro_name!` expression. - fn handle_macro(&self, _db: &dyn SyntaxGroup, _item_ast: ast::ModuleItem) -> Vec { - // Todo(maciektr): Implement. - Vec::new() - } - /// Handle `#[proc_macro_name]` attribute. fn handle_attribute( &self, @@ -176,9 +170,24 @@ impl ProcMacroHostPlugin { .map(|package_id| ProcMacroId::new(package_id, expansion.clone())) } - pub fn build_plugin_suite(macr_host: Arc) -> PluginSuite { + pub fn build_plugin_suite(macro_host: Arc) -> PluginSuite { let mut suite = PluginSuite::default(); - suite.add_plugin_ex(macr_host); + // Register inline macro plugins. + for proc_macro in ¯o_host.macros { + let expansions = proc_macro + .get_expansions() + .iter() + .filter(|exp| matches!(exp.kind, ExpansionKind::Inline)); + for expansion in expansions { + let plugin = Arc::new(ProcMacroInlinePlugin::new( + proc_macro.clone(), + expansion.clone(), + )); + suite.add_inline_macro_plugin_ex(expansion.name.as_str(), plugin); + } + } + // Register procedural macro host plugin. + suite.add_plugin_ex(macro_host); suite } @@ -219,6 +228,13 @@ impl ProcMacroHostPlugin { } Ok(()) } + + pub fn instance(&self, package_id: PackageId) -> &ProcMacroInstance { + self.macros + .iter() + .find(|m| m.package_id() == package_id) + .expect("procedural macro must be registered in proc macro host") + } } impl MacroPlugin for ProcMacroHostPlugin { @@ -230,9 +246,8 @@ impl MacroPlugin for ProcMacroHostPlugin { ) -> PluginResult { // Apply expansion to `item_ast` where needed. let expansions = self - .handle_macro(db, item_ast.clone()) + .handle_attribute(db, item_ast.clone()) .into_iter() - .chain(self.handle_attribute(db, item_ast.clone())) .chain(self.handle_derive(db, item_ast.clone())); let stable_ptr = item_ast.clone().stable_ptr().untyped(); let file_path = stable_ptr.file_id(db).full_path(db.upcast()); @@ -244,12 +259,10 @@ impl MacroPlugin for ProcMacroHostPlugin { let mut modified = false; let mut all_diagnostics: Vec = Vec::new(); for input in expansions { - let instance = self - .macros - .iter() - .find(|m| m.package_id() == input.package_id) - .expect("procedural macro must be registered in proc macro host"); - match instance.generate_code(input.expansion.name.clone(), token_stream.clone()) { + match self + .instance(input.package_id) + .generate_code(input.expansion.name.clone(), token_stream.clone()) + { ProcMacroResult::Replace { token_stream: new_token_stream, aux_data: new_aux_data, @@ -310,6 +323,85 @@ impl MacroPlugin for ProcMacroHostPlugin { } } +/// A Cairo compiler inline macro plugin controlling the inline procedural macro execution. +/// +/// This plugin represents a single expansion capable of handling inline procedural macros. +/// The plugin triggers code expansion in a corresponding procedural macro instance. +#[derive(Debug)] +pub struct ProcMacroInlinePlugin { + instance: Arc, + expansion: Expansion, +} + +impl ProcMacroInlinePlugin { + pub fn new(instance: Arc, expansion: Expansion) -> Self { + assert!(instance.get_expansions().contains(&expansion)); + Self { + instance, + expansion, + } + } + + pub fn name(&self) -> &str { + self.expansion.name.as_str() + } + + fn instance(&self) -> &ProcMacroInstance { + &self.instance + } +} + +impl InlineMacroExprPlugin for ProcMacroInlinePlugin { + fn generate_code( + &self, + db: &dyn SyntaxGroup, + syntax: &ast::ExprInlineMacro, + ) -> InlinePluginResult { + let stable_ptr = syntax.clone().stable_ptr().untyped(); + + let token_stream = TokenStream::from_syntax_node(db, syntax.as_syntax_node()); + match self + .instance() + .generate_code(self.expansion.name.clone(), token_stream) + { + ProcMacroResult::Replace { + token_stream, + aux_data, + diagnostics, + } => { + let aux_data = aux_data.map(|aux_data| { + let aux_data = ProcMacroAuxData::new( + aux_data.into(), + ProcMacroId::new(self.instance.package_id(), self.expansion.clone()), + ); + let mut emitted = EmittedAuxData::default(); + emitted.push(aux_data); + DynGeneratedFileAuxData::new(emitted) + }); + + InlinePluginResult { + code: Some(PluginGeneratedFile { + name: "inline_proc_macro".into(), + content: token_stream.to_string(), + code_mappings: Default::default(), + aux_data, + }), + diagnostics: into_cairo_diagnostics(diagnostics, stable_ptr), + } + } + ProcMacroResult::Remove { diagnostics } => InlinePluginResult { + code: None, + diagnostics: into_cairo_diagnostics(diagnostics, stable_ptr), + }, + ProcMacroResult::Leave { .. } => { + // Safe to panic, as all inline macros should originally return `InlineProcMacroResult`. + // Which is validated inside the inline macro helper attribute. + panic!("inline macro cannot return `Leave` result"); + } + } + } +} + fn into_cairo_diagnostics( diagnostics: Vec, stable_ptr: SyntaxStablePtrId,