From f6b1218375df2f6aa5537a3fa2a6d15cbd7f2643 Mon Sep 17 00:00:00 2001 From: "K.J. Valencik" Date: Tue, 2 Apr 2024 14:47:43 -0400 Subject: [PATCH] feat(neon-macros): Export const and static --- Cargo.lock | 17 ++++---- crates/neon-macros/Cargo.toml | 3 +- crates/neon-macros/src/export/global/meta.rs | 34 +++++++++++++++ crates/neon-macros/src/export/global/mod.rs | 44 ++++++++++++++++++++ crates/neon-macros/src/export/mod.rs | 43 +++++++++++++++++++ crates/neon-macros/src/lib.rs | 41 ++++++++++++++++++ 6 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 crates/neon-macros/src/export/global/meta.rs create mode 100644 crates/neon-macros/src/export/global/mod.rs create mode 100644 crates/neon-macros/src/export/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b7148c363..065d7b326 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.55", + "syn 2.0.57", "which", ] @@ -323,7 +323,7 @@ checksum = "adf157a4dc5a29b7b464aa8fe7edeff30076e07e13646a1c3874f58477dc99f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -398,8 +398,9 @@ dependencies = [ name = "neon-macros" version = "1.0.0" dependencies = [ + "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -518,7 +519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -691,7 +692,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] @@ -736,9 +737,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -762,7 +763,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.57", ] [[package]] diff --git a/crates/neon-macros/Cargo.toml b/crates/neon-macros/Cargo.toml index 857e66257..4e8d34887 100644 --- a/crates/neon-macros/Cargo.toml +++ b/crates/neon-macros/Cargo.toml @@ -11,5 +11,6 @@ edition = "2021" proc-macro = true [dependencies] +proc-macro2 = "1.0.79" quote = "1.0.33" -syn = "2.0.39" +syn = { version = "2.0.57", features = ["full"] } diff --git a/crates/neon-macros/src/export/global/meta.rs b/crates/neon-macros/src/export/global/meta.rs new file mode 100644 index 000000000..91b392727 --- /dev/null +++ b/crates/neon-macros/src/export/global/meta.rs @@ -0,0 +1,34 @@ +#[derive(Default)] +pub(crate) struct Meta { + pub(super) name: Option, + pub(super) json: bool, +} + +pub(crate) struct Parser; + +impl syn::parse::Parser for Parser { + type Output = Meta; + + fn parse2(self, tokens: proc_macro2::TokenStream) -> syn::Result { + let mut attr = Meta::default(); + let parser = syn::meta::parser(|meta| { + if meta.path.is_ident("name") { + attr.name = Some(meta.value()?.parse::()?); + + return Ok(()); + } + + if meta.path.is_ident("json") { + attr.json = true; + + return Ok(()); + } + + Err(meta.error("unsupported property")) + }); + + parser.parse2(tokens)?; + + Ok(attr) + } +} diff --git a/crates/neon-macros/src/export/global/mod.rs b/crates/neon-macros/src/export/global/mod.rs new file mode 100644 index 000000000..a76770db5 --- /dev/null +++ b/crates/neon-macros/src/export/global/mod.rs @@ -0,0 +1,44 @@ +pub(crate) mod meta; + +// Create a new block expression for the RHS of an assignment +pub(super) fn export(meta: meta::Meta, name: &syn::Ident, expr: Box) -> Box { + // Name for the registered create function + let create_name = quote::format_ident!("__NEON_EXPORT_CREATE__{name}"); + + // Default export name as identity unless a name is provided + let export_name = meta + .name + .map(|name| quote::quote!(#name)) + .unwrap_or_else(|| quote::quote!(stringify!(#name))); + + // If `json` is enabled, wrap the value in `Json` before `TryIntoJs` is called + let value = meta + .json + .then(|| quote::quote!(neon::types::extract::Json(&#name))) + .unwrap_or_else(|| quote::quote!(#name)); + + // Generate the function that is registered to create the global on addon initialization. + // Braces are included to prevent names from polluting user code. + let create_fn = quote::quote!({ + #[doc(hidden)] + #[neon::macro_internal::linkme::distributed_slice(neon::macro_internal::EXPORTS)] + #[linkme(crate = neon::macro_internal::linkme)] + fn #create_name<'cx>( + cx: &mut neon::context::ModuleContext<'cx>, + ) -> neon::result::NeonResult<(&'static str, neon::handle::Handle<'cx, neon::types::JsValue>)> { + neon::types::extract::TryIntoJs::try_into_js(#value, cx).map(|v| ( + #export_name, + neon::handle::Handle::upcast(&v), + )) + } + }); + + // Create a block to hold the original expression and the registered crate function + let expr = quote::quote!({ + #create_fn + #expr + }); + + // Create an expression from the token stream + Box::new(syn::Expr::Verbatim(expr)) +} diff --git a/crates/neon-macros/src/export/mod.rs b/crates/neon-macros/src/export/mod.rs new file mode 100644 index 000000000..9f56e344b --- /dev/null +++ b/crates/neon-macros/src/export/mod.rs @@ -0,0 +1,43 @@ +mod global; + +// N.B.: Meta attribute parsing happens in this function because `syn::parse_macro_input!` +// must be called from a function that returns `proc_macro::TokenStream`. +pub(crate) fn export( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + // Parse item to determine the type of export + let item = syn::parse_macro_input!(item as syn::Item); + + match item { + // Export a `const` + syn::Item::Const(mut item) => { + let meta = syn::parse_macro_input!(attr with global::meta::Parser); + + item.expr = global::export(meta, &item.ident, item.expr); + + quote::quote!(#item).into() + } + + // Export a `static` + syn::Item::Static(mut item) => { + let meta = syn::parse_macro_input!(attr with global::meta::Parser); + + item.expr = global::export(meta, &item.ident, item.expr); + + quote::quote!(#item).into() + } + + // Return an error span for all other types + _ => unsupported(item), + } +} + +// Generate an error for unsupported item types +fn unsupported(item: syn::Item) -> proc_macro::TokenStream { + let span = syn::spanned::Spanned::span(&item); + let msg = "`neon::export` can only be applied to consts, and statics."; + let err = syn::Error::new(span, msg); + + err.into_compile_error().into() +} diff --git a/crates/neon-macros/src/lib.rs b/crates/neon-macros/src/lib.rs index 14575ea88..4075e7047 100644 --- a/crates/neon-macros/src/lib.rs +++ b/crates/neon-macros/src/lib.rs @@ -1,5 +1,7 @@ //! Procedural macros supporting [Neon](https://docs.rs/neon/latest/neon/) +mod export; + #[proc_macro_attribute] /// Marks a function as the main entry point for initialization in /// a Neon module. @@ -58,3 +60,42 @@ pub fn main( ) .into() } + +#[proc_macro_attribute] +/// Register an item to be exported by the Neon addon +/// +/// ## Exporting constants and statics +/// +/// ```ignore +/// #[neon::export] +/// static GREETING: &str = "Hello, Neon!"; +/// +/// #[neon::export] +/// const ANSWER: u8 = 42; +/// ``` +/// +/// ### Renaming an export +/// +/// By default, items will be exported with their Rust name. Exports may +/// be renamed by providing the `name` attribute. +/// +/// ```ignore +/// #[neon::export(name = "myGreeting")] +/// static GREETING: &str = "Hello, Neon!"; +/// ``` +/// +/// ### JSON exports +/// +/// Complex values may be exported by automatically serializing to JSON and +/// parsing in JavaScript. Any type that implements `serde::Serialize` may be used. +/// +/// ```ignore +/// #[neon::export] +/// static MESSAGES: &[&str] = &["hello", "goodbye"]; +/// ``` +pub fn export( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + export::export(attr, item) +}