From 6994f0c05c8133664a0e6a1d053623f5e4f11d8a Mon Sep 17 00:00:00 2001 From: "K.J. Valencik" Date: Tue, 16 Jul 2024 08:33:31 -0400 Subject: [PATCH 1/2] Remove unused type --- crates/neon/src/sys/raw.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/neon/src/sys/raw.rs b/crates/neon/src/sys/raw.rs index 82f2089fd..30bdd58ce 100644 --- a/crates/neon/src/sys/raw.rs +++ b/crates/neon/src/sys/raw.rs @@ -47,6 +47,3 @@ impl Default for EscapableHandleScope { Self::new() } } - -#[derive(Clone, Copy)] -pub struct InheritedHandleScope; From 5b6f345145787b5308d14586cd6999f09b688116 Mon Sep 17 00:00:00 2001 From: "K.J. Valencik" Date: Tue, 16 Jul 2024 12:14:11 -0400 Subject: [PATCH 2/2] improve(neon): Use autoref specialization for JSON wrapping return values in exported functions https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md --- .../neon-macros/src/export/function/meta.rs | 11 ---- crates/neon-macros/src/export/function/mod.rs | 57 +++++------------ crates/neon/src/macro_internal/mod.rs | 64 ++++++++++++++++++- crates/neon/src/macros.rs | 17 ----- 4 files changed, 79 insertions(+), 70 deletions(-) diff --git a/crates/neon-macros/src/export/function/meta.rs b/crates/neon-macros/src/export/function/meta.rs index ebaaf6819..77858bd3d 100644 --- a/crates/neon-macros/src/export/function/meta.rs +++ b/crates/neon-macros/src/export/function/meta.rs @@ -4,7 +4,6 @@ pub(crate) struct Meta { pub(super) name: Option, pub(super) json: bool, pub(super) context: bool, - pub(super) result: bool, } #[derive(Default)] @@ -38,12 +37,6 @@ 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)); @@ -75,10 +68,6 @@ 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 837d21ef2..5177815a4 100644 --- a/crates/neon-macros/src/export/function/mod.rs +++ b/crates/neon-macros/src/export/function/mod.rs @@ -40,13 +40,19 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS .unwrap_or_else(|| quote::quote!(#name)) }); - // If necessary, wrap the return value in `Json` before calling `TryIntoJs` - let json_return = meta.json.then(|| { - 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);)) + // Import the value or JSON trait for conversion + let result_trait_name = if meta.json { + quote::format_ident!("NeonExportReturnJson") + } else { + quote::format_ident!("NeonExportReturnValue") + }; + + // Convert the result + // N.B.: Braces are intentionally included to avoid leaking trait to function body + let result_extract = quote::quote!({ + use neon::macro_internal::#result_trait_name; + + res.try_neon_export_return(&mut cx) }); // Default export name as identity unless a name is provided @@ -60,19 +66,13 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS Kind::Normal => quote::quote!( let (#(#tuple_fields,)*) = cx.args()?; let res = #name(#context_arg #(#args),*); - #json_return - neon::types::extract::TryIntoJs::try_into_js(res, &mut cx) - .map(|v| neon::handle::Handle::upcast(&v)) + #result_extract ), Kind::Task => quote::quote!( let (#(#tuple_fields,)*) = cx.args()?; - let promise = neon::context::Context::task(&mut cx, move || { - let res = #name(#context_arg #(#args),*); - #json_return - res - }) - .promise(|mut cx, res| neon::types::extract::TryIntoJs::try_into_js(res, &mut cx)); + let promise = neon::context::Context::task(&mut cx, move || #name(#context_arg #(#args),*)) + .promise(|mut cx, res| #result_extract); Ok(neon::handle::Handle::upcast(&promise)) ), @@ -166,28 +166,3 @@ fn has_context_arg(meta: &meta::Meta, sig: &syn::Signature) -> syn::Result Ok(true) } - -// Determine if a return type is a `Result` -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, - }; - - let path = match ty { - syn::Type::Path(path) => path, - _ => return false, - }; - - let path = match path.path.segments.last() { - Some(path) => path, - None => return false, - }; - - path.ident == "Result" || path.ident == "NeonResult" || path.ident == "JsResult" -} diff --git a/crates/neon/src/macro_internal/mod.rs b/crates/neon/src/macro_internal/mod.rs index 30b7e65e6..1cb6e0b3a 100644 --- a/crates/neon/src/macro_internal/mod.rs +++ b/crates/neon/src/macro_internal/mod.rs @@ -2,7 +2,12 @@ pub use linkme; -use crate::{context::ModuleContext, handle::Handle, result::NeonResult, types::JsValue}; +use crate::{ + context::{Context, ModuleContext}, + handle::Handle, + result::{JsResult, NeonResult}, + types::{extract::TryIntoJs, JsValue}, +}; type Export<'cx> = (&'static str, Handle<'cx, JsValue>); @@ -11,3 +16,60 @@ pub static EXPORTS: [for<'cx> fn(&mut ModuleContext<'cx>) -> NeonResult fn(ModuleContext<'cx>) -> NeonResult<()>]; + +// Provides an identically named method to `NeonExportReturnJson` for easy swapping in macros +pub trait NeonExportReturnValue<'cx> { + fn try_neon_export_return(self, cx: &mut C) -> JsResult<'cx, JsValue> + where + C: Context<'cx>; +} + +impl<'cx, T> NeonExportReturnValue<'cx> for T +where + T: TryIntoJs<'cx>, +{ + fn try_neon_export_return(self, cx: &mut C) -> JsResult<'cx, JsValue> + where + C: Context<'cx>, + { + self.try_into_js(cx).map(|v| v.upcast()) + } +} + +#[cfg(feature = "serde")] +// Trait used for specializing `Json` wrapping of `T` or `Result` in macros +// Leverages the [autoref specialization](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) technique +pub trait NeonExportReturnJson<'cx> { + fn try_neon_export_return(self, cx: &mut C) -> JsResult<'cx, JsValue> + where + C: Context<'cx>; +} + +#[cfg(feature = "serde")] +// More specific behavior wraps `Result::Ok` in `Json` +impl<'cx, T, E> NeonExportReturnJson<'cx> for Result +where + T: serde::Serialize, + E: TryIntoJs<'cx>, +{ + fn try_neon_export_return(self, cx: &mut C) -> JsResult<'cx, JsValue> + where + C: Context<'cx>, + { + self.map(crate::types::extract::Json).try_into_js(cx) + } +} + +#[cfg(feature = "serde")] +// Due to autoref behavior, this is less specific than the other implementation +impl<'cx, T> NeonExportReturnJson<'cx> for &T +where + T: serde::Serialize, +{ + fn try_neon_export_return(self, cx: &mut C) -> JsResult<'cx, JsValue> + where + C: Context<'cx>, + { + crate::types::extract::Json(self).try_into_js(cx) + } +} diff --git a/crates/neon/src/macros.rs b/crates/neon/src/macros.rs index bab7f086a..b32c838f6 100644 --- a/crates/neon/src/macros.rs +++ b/crates/neon/src/macros.rs @@ -194,21 +194,4 @@ pub use neon_macros::main; /// 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;