Skip to content

Commit

Permalink
feat(neon-macros): Export const and static
Browse files Browse the repository at this point in the history
  • Loading branch information
kjvalencik committed Apr 2, 2024
1 parent 50705bd commit f6b1218
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 9 deletions.
17 changes: 9 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/neon-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
34 changes: 34 additions & 0 deletions crates/neon-macros/src/export/global/meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#[derive(Default)]
pub(crate) struct Meta {
pub(super) name: Option<syn::LitStr>,
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<Self::Output> {
let mut attr = Meta::default();
let parser = syn::meta::parser(|meta| {
if meta.path.is_ident("name") {
attr.name = Some(meta.value()?.parse::<syn::LitStr>()?);

return Ok(());
}

if meta.path.is_ident("json") {
attr.json = true;

return Ok(());
}

Err(meta.error("unsupported property"))
});

parser.parse2(tokens)?;

Ok(attr)
}
}
44 changes: 44 additions & 0 deletions crates/neon-macros/src/export/global/mod.rs
Original file line number Diff line number Diff line change
@@ -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<syn::Expr>) -> Box<syn::Expr> {
// 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))
}
43 changes: 43 additions & 0 deletions crates/neon-macros/src/export/mod.rs
Original file line number Diff line number Diff line change
@@ -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()
}
41 changes: 41 additions & 0 deletions crates/neon-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
}

0 comments on commit f6b1218

Please sign in to comment.