From 1198c4cf7d02c50510e132f0ffaa7584dac78907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Sun, 12 May 2024 21:20:47 +0200 Subject: [PATCH 1/6] feat: add impl-level exporting macro --- crates/rune-macros/Cargo.toml | 2 +- crates/rune-macros/src/function.rs | 14 ++--- crates/rune-macros/src/item_impl.rs | 92 +++++++++++++++++++++++++++++ crates/rune-macros/src/lib.rs | 17 ++++++ crates/rune-macros/tests/derive.rs | 24 ++++++++ 5 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 crates/rune-macros/src/item_impl.rs 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..bc4c245ce --- /dev/null +++ b/crates/rune-macros/src/item_impl.rs @@ -0,0 +1,92 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::parse::ParseStream; +use syn::punctuated::Punctuated; + +pub(crate) struct ItemImplAttrs { + /// Name of the exporter function + name: syn::Ident, +} + +impl ItemImplAttrs { + pub(crate) fn parse(input: ParseStream) -> syn::Result { + let name = input + .parse::() + .unwrap_or_else(|_| syn::Ident::new("export_rune_methods", input.span())); + Ok(Self { name }) + } +} + +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!(#[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( + [ + syn::PathSegment::from(::default()), + syn::PathSegment::from(name.clone()), + ] + .into_iter(), + ), + }; + + let docs = reparsed.docs; + let arguments = reparsed.arguments; + + let meta = quote! { + rune::__private::FunctionMetaData { + kind: rune::__private::FunctionMetaKind::instance(#name_string, #path)?, + name: #name_string, + deprecated: None, + docs: &#docs[..], + arguments: &#arguments[..], + } + }; + + export_list.push(meta); + } + } + } + + let name = attrs.name; + + let export_count = export_list.len(); + let exporter = quote! { + fn #name() -> rune::alloc::Result<[rune::__private::FunctionMetaData; #export_count]> { + Ok([ #(#export_list),* ]) + } + }; + + block.items.push(syn::parse2(exporter).unwrap()); + + Ok(block.to_token_stream().into()) + } +} diff --git a/crates/rune-macros/src/lib.rs b/crates/rune-macros/src/lib.rs index adf5b22c9..c93ffe542 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,22 @@ pub fn function( output.into() } +#[proc_macro_attribute] +pub fn impl_item( + 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..6b412d82d 100644 --- a/crates/rune-macros/tests/derive.rs +++ b/crates/rune-macros/tests/derive.rs @@ -17,3 +17,27 @@ fn generic_derive() { value: T, } } + +#[test] +fn export_impl() { + #[derive(crate::Any)] + struct MyStruct(usize); + + #[crate::impl_item] + impl MyStruct { + #[export] + pub fn foo(&self) -> usize { + self.0 + } + } + + #[crate::impl_item(export_rune_api_extension)] + impl MyStruct { + #[export] + pub fn bar(&self) -> usize { + self.0 + 1 + } + } + + assert!(MyStruct(2).foo() + 1 == MyStruct(2).bar()); +} From 65c6dddfc9bc4ebfae40fd5be69bc4513d939ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Sun, 12 May 2024 21:50:29 +0200 Subject: [PATCH 2/6] feat: add usage example and required module func --- crates/rune-macros/tests/derive.rs | 12 ++++++++++++ crates/rune/src/module/module.rs | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/crates/rune-macros/tests/derive.rs b/crates/rune-macros/tests/derive.rs index 6b412d82d..455121231 100644 --- a/crates/rune-macros/tests/derive.rs +++ b/crates/rune-macros/tests/derive.rs @@ -37,6 +37,18 @@ fn export_impl() { pub fn bar(&self) -> usize { self.0 + 1 } + + pub fn rune_export( + mut module: rune::Module, + ) -> rune::alloc::Result> { + for func in Self::export_rune_api_extension()? { + if let Err(e) = module.function_from_meta(func) { + return Ok(Err(e)); + } + } + + Ok(Ok(module)) + } } assert!(MyStruct(2).foo() + 1 == MyStruct(2).bar()); 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; From 29a2f4176136aa5681d2fcec0f1abd091ced0d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 13 May 2024 01:52:17 +0200 Subject: [PATCH 3/6] style: fix clippy error --- crates/rune-macros/src/item_impl.rs | 2 +- crates/rune-macros/tests/derive.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rune-macros/src/item_impl.rs b/crates/rune-macros/src/item_impl.rs index bc4c245ce..38fded576 100644 --- a/crates/rune-macros/src/item_impl.rs +++ b/crates/rune-macros/src/item_impl.rs @@ -87,6 +87,6 @@ impl ItemImpl { block.items.push(syn::parse2(exporter).unwrap()); - Ok(block.to_token_stream().into()) + Ok(block.to_token_stream()) } } diff --git a/crates/rune-macros/tests/derive.rs b/crates/rune-macros/tests/derive.rs index 455121231..61a75e880 100644 --- a/crates/rune-macros/tests/derive.rs +++ b/crates/rune-macros/tests/derive.rs @@ -21,7 +21,7 @@ fn generic_derive() { #[test] fn export_impl() { #[derive(crate::Any)] - struct MyStruct(usize); + struct MyStruct(#[rune(get)] usize); #[crate::impl_item] impl MyStruct { From 7749a76383a75860bcc7ccc2768e7539557d33d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 20 May 2024 13:47:52 +0200 Subject: [PATCH 4/6] feat: add exporter function gen, small attr change --- crates/rune-macros/src/item_impl.rs | 65 +++++++++++++++++++++++++---- crates/rune-macros/src/lib.rs | 34 ++++++++++++++- crates/rune-macros/tests/derive.rs | 10 +++-- 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/crates/rune-macros/src/item_impl.rs b/crates/rune-macros/src/item_impl.rs index 38fded576..0a35733c3 100644 --- a/crates/rune-macros/src/item_impl.rs +++ b/crates/rune-macros/src/item_impl.rs @@ -2,18 +2,51 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::parse::ParseStream; use syn::punctuated::Punctuated; +use syn::Token; pub(crate) struct ItemImplAttrs { /// Name of the exporter function - name: syn::Ident, + list: syn::Ident, + 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 name = input - .parse::() - .unwrap_or_else(|_| syn::Ident::new("export_rune_methods", input.span())); - Ok(Self { name }) + 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) } } @@ -76,16 +109,30 @@ impl ItemImpl { } } - let name = attrs.name; + let name = attrs.list; let export_count = export_list.len(); - let exporter = quote! { - fn #name() -> rune::alloc::Result<[rune::__private::FunctionMetaData; #export_count]> { + 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).unwrap()); + 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 c93ffe542..5740cbdd9 100644 --- a/crates/rune-macros/src/lib.rs +++ b/crates/rune-macros/src/lib.rs @@ -78,8 +78,40 @@ 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 impl_item( +pub fn item_impl( attrs: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { diff --git a/crates/rune-macros/tests/derive.rs b/crates/rune-macros/tests/derive.rs index 61a75e880..b0bcbb97b 100644 --- a/crates/rune-macros/tests/derive.rs +++ b/crates/rune-macros/tests/derive.rs @@ -23,7 +23,7 @@ fn export_impl() { #[derive(crate::Any)] struct MyStruct(#[rune(get)] usize); - #[crate::impl_item] + #[crate::item_impl(exporter = export_rune_api)] impl MyStruct { #[export] pub fn foo(&self) -> usize { @@ -31,7 +31,7 @@ fn export_impl() { } } - #[crate::impl_item(export_rune_api_extension)] + #[crate::item_impl(list = rune_api_extension, exporter = export_rune_api_extension)] impl MyStruct { #[export] pub fn bar(&self) -> usize { @@ -41,7 +41,10 @@ fn export_impl() { pub fn rune_export( mut module: rune::Module, ) -> rune::alloc::Result> { - for func in Self::export_rune_api_extension()? { + 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)); } @@ -52,4 +55,5 @@ fn export_impl() { } assert!(MyStruct(2).foo() + 1 == MyStruct(2).bar()); + assert!(MyStruct::rune_export(rune::Module::new()).is_ok()); } From d53816acb268b59eb4d31da783cc8373606ad595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 20 May 2024 14:20:58 +0200 Subject: [PATCH 5/6] feat: add support for associated functions --- crates/rune-macros/src/item_impl.rs | 32 ++++++++++++++++++++++++----- crates/rune-macros/tests/derive.rs | 6 ++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/crates/rune-macros/src/item_impl.rs b/crates/rune-macros/src/item_impl.rs index 0a35733c3..3f2ac60a1 100644 --- a/crates/rune-macros/src/item_impl.rs +++ b/crates/rune-macros/src/item_impl.rs @@ -2,11 +2,13 @@ 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 exporter function + /// Name of the function meta list function list: syn::Ident, + /// Name of the function export function exporter: Option, } @@ -84,19 +86,39 @@ impl ItemImpl { leading_colon: None, segments: Punctuated::from_iter( [ - syn::PathSegment::from(::default()), - syn::PathSegment::from(name.clone()), + 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(), + .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::instance(#name_string, #path)?, + kind: rune::__private::FunctionMetaKind::#meta_kind(#name_string, #path)?#build_with, name: #name_string, deprecated: None, docs: &#docs[..], diff --git a/crates/rune-macros/tests/derive.rs b/crates/rune-macros/tests/derive.rs index b0bcbb97b..61fe2727d 100644 --- a/crates/rune-macros/tests/derive.rs +++ b/crates/rune-macros/tests/derive.rs @@ -38,6 +38,11 @@ fn export_impl() { self.0 + 1 } + #[export] + pub fn baz() -> usize { + 42 + } + pub fn rune_export( mut module: rune::Module, ) -> rune::alloc::Result> { @@ -56,4 +61,5 @@ fn export_impl() { assert!(MyStruct(2).foo() + 1 == MyStruct(2).bar()); assert!(MyStruct::rune_export(rune::Module::new()).is_ok()); + assert!(MyStruct::baz() == 42); } From 4e9826034da5ac1e40c7b51186358ffc169c2f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 20 May 2024 14:42:14 +0200 Subject: [PATCH 6/6] fix: change export attr, improve test --- crates/rune-macros/src/item_impl.rs | 2 +- crates/rune-macros/tests/derive.rs | 29 +++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/crates/rune-macros/src/item_impl.rs b/crates/rune-macros/src/item_impl.rs index 3f2ac60a1..a7005048d 100644 --- a/crates/rune-macros/src/item_impl.rs +++ b/crates/rune-macros/src/item_impl.rs @@ -59,7 +59,7 @@ impl ItemImpl { let Self(mut block) = self; let mut export_list = Vec::new(); - let export_attr: syn::Attribute = syn::parse_quote!(#[export]); + let export_attr: syn::Attribute = syn::parse_quote!(#[rune(export)]); for item in block.items.iter_mut() { if let syn::ImplItem::Fn(method) = item { diff --git a/crates/rune-macros/tests/derive.rs b/crates/rune-macros/tests/derive.rs index 61fe2727d..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::*; @@ -25,7 +27,7 @@ fn export_impl() { #[crate::item_impl(exporter = export_rune_api)] impl MyStruct { - #[export] + #[rune(export)] pub fn foo(&self) -> usize { self.0 } @@ -33,12 +35,12 @@ fn export_impl() { #[crate::item_impl(list = rune_api_extension, exporter = export_rune_api_extension)] impl MyStruct { - #[export] + #[rune(export)] pub fn bar(&self) -> usize { self.0 + 1 } - #[export] + #[rune(export)] pub fn baz() -> usize { 42 } @@ -59,7 +61,22 @@ fn export_impl() { } } - assert!(MyStruct(2).foo() + 1 == MyStruct(2).bar()); - assert!(MyStruct::rune_export(rune::Module::new()).is_ok()); - assert!(MyStruct::baz() == 42); + 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); }