From 449fed39adc5ee34eb5003125d4865206c48bc51 Mon Sep 17 00:00:00 2001 From: "K.J. Valencik" Date: Mon, 6 May 2024 11:11:42 -0400 Subject: [PATCH] Fix PR review comments --- .../neon-macros/src/export/function/meta.rs | 11 +++++ crates/neon-macros/src/export/function/mod.rs | 44 +++++++++++-------- crates/neon-macros/src/export/global/mod.rs | 5 +++ crates/neon/src/macros.rs | 40 +++++++++++++++++ 4 files changed, 81 insertions(+), 19 deletions(-) diff --git a/crates/neon-macros/src/export/function/meta.rs b/crates/neon-macros/src/export/function/meta.rs index 77858bd3d..ebaaf6819 100644 --- a/crates/neon-macros/src/export/function/meta.rs +++ b/crates/neon-macros/src/export/function/meta.rs @@ -4,6 +4,7 @@ pub(crate) struct Meta { pub(super) name: Option, pub(super) json: bool, pub(super) context: bool, + pub(super) result: bool, } #[derive(Default)] @@ -37,6 +38,12 @@ impl Meta { Ok(()) } + fn force_result(&mut self, _meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { + self.result = true; + + Ok(()) + } + fn make_task(&mut self, meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { if self.context { return Err(meta.error(super::TASK_CX_ERROR)); @@ -68,6 +75,10 @@ impl syn::parse::Parser for Parser { return attr.force_context(meta); } + if meta.path.is_ident("result") { + return attr.force_result(meta); + } + if meta.path.is_ident("task") { return attr.make_task(meta); } diff --git a/crates/neon-macros/src/export/function/mod.rs b/crates/neon-macros/src/export/function/mod.rs index 21b73d424..837d21ef2 100644 --- a/crates/neon-macros/src/export/function/mod.rs +++ b/crates/neon-macros/src/export/function/mod.rs @@ -21,21 +21,14 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS let wrapper_name = quote::format_ident!("__NEON_EXPORT_WRAPPER__{name}"); // Determine if the first argument is `FunctionContext` - let has_context = meta.context - || match has_context_arg(&meta, &sig) { - Ok(has_context) => has_context, - Err(err) => return err.into_compile_error().into(), - }; + let has_context = match has_context_arg(&meta, &sig) { + Ok(has_context) => has_context, + Err(err) => return err.into_compile_error().into(), + }; // Retain the context argument, if necessary let context_arg = has_context.then(|| quote::quote!(&mut cx,)); - // 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))); - // Generate an argument list used when calling the original function let start = if has_context { 1 } else { 0 }; let args = (start..sig.inputs.len()).map(|i| quote::format_ident!("a{i}")); @@ -49,13 +42,19 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS // If necessary, wrap the return value in `Json` before calling `TryIntoJs` let json_return = meta.json.then(|| { - is_result_output(&sig.output) + is_result_output(&meta, &sig.output) // Use `.map(Json)` on a `Result` .then(|| quote::quote!(let res = res.map(neon::types::extract::Json);)) // Wrap other values with `Json(res)` .unwrap_or_else(|| quote::quote!(let res = neon::types::extract::Json(res);)) }); + // 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))); + // Generate the call to the original function let call_body = match meta.kind { Kind::Normal => quote::quote!( @@ -119,7 +118,7 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS } // Get the ident for the first argument -fn first_arg_ident(sig: &syn::Signature) -> Option<&syn::Ident> { +fn first_arg_ty(sig: &syn::Signature) -> Option<&syn::Ident> { let arg = sig.inputs.first()?; let ty = match arg { syn::FnArg::Receiver(v) => &*v.ty, @@ -136,18 +135,20 @@ fn first_arg_ident(sig: &syn::Signature) -> Option<&syn::Ident> { _ => return None, }; - let path = match path.path.segments.last() { - Some(path) => path, - None => return None, - }; + let path = path.path.segments.last()?; Some(&path.ident) } // Determine if the function has a context argument and if it is allowed fn has_context_arg(meta: &meta::Meta, sig: &syn::Signature) -> syn::Result { + // Forced context argument + if meta.context { + return Ok(true); + } + // Return early if no arguments - let first = match first_arg_ident(sig) { + let first = match first_arg_ty(sig) { Some(first) => first, None => return Ok(false), }; @@ -167,7 +168,12 @@ fn has_context_arg(meta: &meta::Meta, sig: &syn::Signature) -> syn::Result } // Determine if a return type is a `Result` -fn is_result_output(ret: &syn::ReturnType) -> bool { +fn is_result_output(meta: &meta::Meta, ret: &syn::ReturnType) -> bool { + // Forced result output + if meta.result { + return true; + } + let ty = match ret { syn::ReturnType::Default => return false, syn::ReturnType::Type(_, ty) => &**ty, diff --git a/crates/neon-macros/src/export/global/mod.rs b/crates/neon-macros/src/export/global/mod.rs index a76770db5..5ded6a9d6 100644 --- a/crates/neon-macros/src/export/global/mod.rs +++ b/crates/neon-macros/src/export/global/mod.rs @@ -19,6 +19,11 @@ pub(super) fn export(meta: meta::Meta, name: &syn::Ident, expr: Box) // Generate the function that is registered to create the global on addon initialization. // Braces are included to prevent names from polluting user code. + // + // N.B.: The `linkme(..)` attribute informs the `distributed_slice(..)` macro where + // to find the `linkme` crate. It is re-exported from neon to avoid dependents from + // needing to adding a direct dependency on `linkme`. It is an undocumented feature. + // https://github.com/dtolnay/linkme/issues/54 let create_fn = quote::quote!({ #[doc(hidden)] #[neon::macro_internal::linkme::distributed_slice(neon::macro_internal::EXPORTS)] diff --git a/crates/neon/src/macros.rs b/crates/neon/src/macros.rs index 39a2fe1d9..f46421fd1 100644 --- a/crates/neon/src/macros.rs +++ b/crates/neon/src/macros.rs @@ -145,4 +145,44 @@ pub use neon_macros::main; /// a + b /// } /// ``` +/// +/// ### Advanced +/// +/// The following attributes are for advanced configuration and may not be +/// necessary for most users. +/// +/// #### `context` +/// +/// The `#[neon::export]` macro looks checks if the first argument has a type of +/// &mut FunctionContext` to determine if the [`Context`](crate::context::Context) +/// should be passed to the function. +/// +/// If the type has been renamed when importing, the `context` attribute can be +/// added to force it to be passed. +/// +/// ``` +/// use neon::context::{FunctionContext as FnCtx}; +/// +/// #[neon::export(context)] +/// fn add(_cx: &mut FnCtx, a: f64, b: f64) -> f64 { +/// a + b +/// } +/// ``` +/// +/// ### `result` +/// +/// The `#[neon::export]` macro will infer an exported function returns a [`Result`] +/// if the type is named [`Result`], [`NeonResult`](crate::result::NeonResult) or +/// [`JsResult`](crate::result::JsResult). +/// +/// If a type alias is used for [`Result`], the `result` attribute can be added to +/// inform the generated code. +/// +/// ``` +/// use neon::result::{NeonResult as Res}; +/// +/// fn add(a: f64, b: f64) -> Res { +/// Ok(a + b) +/// } +/// ``` pub use neon_macros::export;