diff --git a/crates/rune-macros/Cargo.toml b/crates/rune-macros/Cargo.toml index f8df9df87..c12612b7f 100644 --- a/crates/rune-macros/Cargo.toml +++ b/crates/rune-macros/Cargo.toml @@ -15,7 +15,7 @@ categories = ["parser-implementations"] [dependencies] rune-core = { version = "=0.14.0", path = "../rune-core", features = ["std"] } -syn = { version = "2.0.16", features = ["full"] } +syn = { version = "2.0.16", features = ["full", "extra-traits"] } quote = "1.0.27" proc-macro2 = "1.0.56" diff --git a/crates/rune-macros/src/function.rs b/crates/rune-macros/src/function.rs index 135e77060..fa9328296 100644 --- a/crates/rune-macros/src/function.rs +++ b/crates/rune-macros/src/function.rs @@ -116,13 +116,13 @@ impl FunctionAttrs { } pub(crate) struct Function { - attributes: Vec, - vis: syn::Visibility, - sig: syn::Signature, - remainder: TokenStream, - docs: syn::ExprArray, - arguments: syn::ExprArray, - takes_self: bool, + pub attributes: Vec, + pub vis: syn::Visibility, + pub sig: syn::Signature, + pub remainder: TokenStream, + pub docs: syn::ExprArray, + pub arguments: syn::ExprArray, + pub takes_self: bool, } impl Function { diff --git a/crates/rune-macros/src/item_impl.rs b/crates/rune-macros/src/item_impl.rs new file mode 100644 index 000000000..a7005048d --- /dev/null +++ b/crates/rune-macros/src/item_impl.rs @@ -0,0 +1,161 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::parse::ParseStream; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::Token; + +pub(crate) struct ItemImplAttrs { + /// Name of the function meta list function + list: syn::Ident, + /// Name of the function export function + exporter: Option, +} + +impl Default for ItemImplAttrs { + fn default() -> Self { + Self { + list: syn::Ident::new("rune_api", proc_macro2::Span::call_site()), + exporter: None, + } + } +} + +impl ItemImplAttrs { + const LIST_IDENT: &'static str = "list"; + const EXPORTER_IDENT: &'static str = "exporter"; + + pub(crate) fn parse(input: ParseStream) -> syn::Result { + let mut attrs = Self::default(); + + while !input.is_empty() { + let ident = input.parse::()?; + + match ident.to_string().as_str() { + Self::LIST_IDENT | Self::EXPORTER_IDENT => { + input.parse::()?; + if ident == Self::LIST_IDENT { + attrs.list = input.parse()?; + } else { + attrs.exporter = Some(input.parse()?); + } + } + _ => return Err(syn::Error::new_spanned(ident, "Unsupported option")), + } + + if input.parse::>()?.is_none() { + break; + } + } + + Ok(attrs) + } +} + +pub(crate) struct ItemImpl(pub syn::ItemImpl); + +impl ItemImpl { + pub(crate) fn expand(self, attrs: ItemImplAttrs) -> syn::Result { + let Self(mut block) = self; + + let mut export_list = Vec::new(); + let export_attr: syn::Attribute = syn::parse_quote!(#[rune(export)]); + + for item in block.items.iter_mut() { + if let syn::ImplItem::Fn(method) = item { + let attr_index = method + .attrs + .iter() + .enumerate() + .find_map(|(index, attr)| (*attr == export_attr).then_some(index)); + + if let Some(index) = attr_index { + method.attrs.remove(index); + + let reparsed = syn::parse::Parser::parse2( + crate::function::Function::parse, + method.to_token_stream(), + )?; + + let name = method.sig.ident.clone(); + let name_string = syn::LitStr::new( + &reparsed.sig.ident.to_string(), + reparsed.sig.ident.span(), + ); + let path = syn::Path { + leading_colon: None, + segments: Punctuated::from_iter( + [ + reparsed + .takes_self + .then(|| syn::PathSegment::from(::default())) + .or_else(|| { + Some(syn::PathSegment::from( + syn::parse2::( + block.self_ty.to_token_stream(), + ) + .unwrap(), + )) + }), + Some(syn::PathSegment::from(name.clone())), + ] + .into_iter() + .flatten(), + ), + }; + + let docs = reparsed.docs; + let arguments = reparsed.arguments; + let meta_kind = syn::Ident::new( + ["function", "instance"][reparsed.takes_self as usize], + reparsed.sig.span(), + ); + let build_with = if reparsed.takes_self { + None + } else { + Some(quote!(.build()?)) + }; + + let meta = quote! { + rune::__private::FunctionMetaData { + kind: rune::__private::FunctionMetaKind::#meta_kind(#name_string, #path)?#build_with, + name: #name_string, + deprecated: None, + docs: &#docs[..], + arguments: &#arguments[..], + } + }; + + export_list.push(meta); + } + } + } + + let name = attrs.list; + + let export_count = export_list.len(); + let list_function = quote! { + fn #name() -> ::rune::alloc::Result<[::rune::__private::FunctionMetaData; #export_count]> { + Ok([ #(#export_list),* ]) + } + }; + block.items.push(syn::parse2(list_function).unwrap()); + + if let Some(exporter_name) = attrs.exporter { + let exporter_function = quote! { + fn #exporter_name(mut module: ::rune::Module) -> ::rune::alloc::Result> { + for meta in Self::#name()? { + if let Err(e) = module.function_from_meta(meta) { + return Ok(Err(e)); + } + } + Ok(Ok(module)) + } + }; + + block.items.push(syn::parse2(exporter_function).unwrap()); + } + + Ok(block.to_token_stream()) + } +} diff --git a/crates/rune-macros/src/lib.rs b/crates/rune-macros/src/lib.rs index adf5b22c9..5740cbdd9 100644 --- a/crates/rune-macros/src/lib.rs +++ b/crates/rune-macros/src/lib.rs @@ -37,6 +37,7 @@ mod hash; mod inst_display; mod instrument; mod internals; +mod item_impl; mod macro_; mod module; mod opaque; @@ -77,6 +78,54 @@ pub fn function( output.into() } +/// Create a function to export all functions marked with the `#[rune(export)]` attribute within a module. +/// +/// ### Example +/// +/// ```rs +/// #[derive(rune::Any)] +/// struct MyStruct { +/// field: u32, +/// } +/// +/// #[rune::item_impl(exporter = export_rune_api)] +/// impl MyStruct { +/// // Exported +/// #[rune(export)] +/// fn foo(&self) -> u32 { +/// self.field + 1 +/// } +/// +/// // Not exported +/// fn bar(&self) -> u32 { +/// self.field + 2 +/// } +/// } +/// +/// fn main() { +/// let mut module = rune::Module::new(); +/// module.ty::().unwrap(); +/// module = MyStruct::export_rune_api(module) +/// .expect("Allocation error") +/// .expect("Context error"); +/// } +/// ``` +#[proc_macro_attribute] +pub fn item_impl( + attrs: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let attrs = syn::parse_macro_input!(attrs with crate::item_impl::ItemImplAttrs::parse); + let item = crate::item_impl::ItemImpl(syn::parse_macro_input!(item as syn::ItemImpl)); + + let output = match item.expand(attrs) { + Ok(output) => output, + Err(e) => return proc_macro::TokenStream::from(e.to_compile_error()), + }; + + output.into() +} + #[proc_macro_attribute] pub fn macro_( attrs: proc_macro::TokenStream, diff --git a/crates/rune-macros/tests/derive.rs b/crates/rune-macros/tests/derive.rs index 489fc8436..1d47defb1 100644 --- a/crates/rune-macros/tests/derive.rs +++ b/crates/rune-macros/tests/derive.rs @@ -1,3 +1,5 @@ +use std::fmt::Debug; + use rune::T; use rune_macros::*; @@ -17,3 +19,64 @@ fn generic_derive() { value: T, } } + +#[test] +fn export_impl() { + #[derive(crate::Any)] + struct MyStruct(#[rune(get)] usize); + + #[crate::item_impl(exporter = export_rune_api)] + impl MyStruct { + #[rune(export)] + pub fn foo(&self) -> usize { + self.0 + } + } + + #[crate::item_impl(list = rune_api_extension, exporter = export_rune_api_extension)] + impl MyStruct { + #[rune(export)] + pub fn bar(&self) -> usize { + self.0 + 1 + } + + #[rune(export)] + pub fn baz() -> usize { + 42 + } + + pub fn rune_export( + mut module: rune::Module, + ) -> rune::alloc::Result> { + for func in Self::rune_api()? + .into_iter() + .chain(Self::rune_api_extension()?.into_iter()) + { + if let Err(e) = module.function_from_meta(func) { + return Ok(Err(e)); + } + } + + Ok(Ok(module)) + } + } + + let a = MyStruct(2); + assert_eq!(a.foo() + 1, a.bar()); + + fn test_fn(f: F) + where + E: Debug, + F: Fn(rune::Module) -> Result, + { + let mut m = rune::Module::new(); + m.ty::().unwrap(); + f(m).unwrap(); + } + + test_fn(MyStruct::rune_export); + test_fn(MyStruct::export_rune_api); + test_fn(MyStruct::export_rune_api_extension); + + assert_eq!(MyStruct::baz(), 42); +} diff --git a/crates/rune/src/module/module.rs b/crates/rune/src/module/module.rs index 1f235b85e..19c881a7a 100644 --- a/crates/rune/src/module/module.rs +++ b/crates/rune/src/module/module.rs @@ -25,6 +25,8 @@ use crate::runtime::{ }; use crate::Hash; +use super::FunctionMetaData; + /// Function builder as returned by [`Module::function`]. /// /// This allows for building a function regularly with @@ -1242,7 +1244,14 @@ impl Module { #[inline] pub fn function_meta(&mut self, meta: FunctionMeta) -> Result, ContextError> { let meta = meta()?; + self.function_from_meta(meta) + } + /// Register a function handler through its metadata. + pub fn function_from_meta( + &mut self, + meta: FunctionMetaData, + ) -> Result, ContextError> { match meta.kind { FunctionMetaKind::Function(data) => { let mut docs = Docs::EMPTY;