diff --git a/crates/neon-macros/src/export/function/mod.rs b/crates/neon-macros/src/export/function/mod.rs index fb49fdf9e..994cecb5b 100644 --- a/crates/neon-macros/src/export/function/mod.rs +++ b/crates/neon-macros/src/export/function/mod.rs @@ -42,19 +42,19 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS .unwrap_or_else(|| quote::quote!(#name)) }); - // Import the value or JSON trait for conversion - let result_trait_name = if meta.json { - quote::format_ident!("NeonExportReturnJson") + // Tag whether we should JSON wrap results + let return_tag = if meta.json { + quote::format_ident!("NeonJsonTag") } else { - quote::format_ident!("NeonExportReturnValue") + quote::format_ident!("NeonValueTag") }; // 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; + use neon::macro_internal::{ToNeonMarker, #return_tag as NeonReturnTag}; - res.try_neon_export_return(&mut cx) + (&res).to_neon_marker::().neon_into_js(&mut cx, res) }); // Default export name as identity unless a name is provided @@ -69,9 +69,9 @@ pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenS let (#(#tuple_fields,)*) = cx.args()?; let fut = #name(#context_arg #(#args),*); let fut = { - use neon::macro_internal::ToNeonFutureMarker; + use neon::macro_internal::{ToNeonMarker, NeonValueTag}; - (&fut).to_neon_future_marker().make_result(&mut cx, fut)? + (&fut).to_neon_marker::().into_neon_result(&mut cx, fut)? }; neon::macro_internal::spawn(&mut cx, fut, |mut cx, res| #result_extract) diff --git a/crates/neon/src/macro_internal/futures.rs b/crates/neon/src/macro_internal/futures.rs index 1e1aafbf6..d837e1347 100644 --- a/crates/neon/src/macro_internal/futures.rs +++ b/crates/neon/src/macro_internal/futures.rs @@ -2,8 +2,8 @@ use std::future::Future; use crate::{ context::{Context, Cx, TaskContext}, - result::{JsResult, NeonResult}, - types::{extract::TryIntoJs, JsValue}, + result::JsResult, + types::JsValue, }; pub fn spawn<'cx, F, S>(cx: &mut Cx<'cx>, fut: F, settle: S) -> JsResult<'cx, JsValue> @@ -27,46 +27,3 @@ where Ok(promise.upcast()) } - -pub trait ToNeonFutureMarker { - type Marker; - - fn to_neon_future_marker(&self) -> Self::Marker; -} - -impl ToNeonFutureMarker for Result { - type Marker = NeonFutureMarkerResult; - - fn to_neon_future_marker(&self) -> Self::Marker { - NeonFutureMarkerResult - } -} - -impl ToNeonFutureMarker for &T { - type Marker = NeonFutureMarkerValue; - - fn to_neon_future_marker(&self) -> Self::Marker { - NeonFutureMarkerValue - } -} - -pub struct NeonFutureMarkerResult; -pub struct NeonFutureMarkerValue; - -impl NeonFutureMarkerResult { - pub fn make_result<'cx, T, E>(self, cx: &mut Cx<'cx>, res: Result) -> NeonResult - where - E: TryIntoJs<'cx>, - { - res.or_else(|err| { - let err = err.try_into_js(cx)?; - cx.throw(err) - }) - } -} - -impl NeonFutureMarkerValue { - pub fn make_result(self, _cx: &mut Cx, res: T) -> NeonResult { - Ok(res) - } -} diff --git a/crates/neon/src/macro_internal/mod.rs b/crates/neon/src/macro_internal/mod.rs index 846aa2730..06ef021a6 100644 --- a/crates/neon/src/macro_internal/mod.rs +++ b/crates/neon/src/macro_internal/mod.rs @@ -1,14 +1,19 @@ //! Internals needed by macros. These have to be exported for the macros to work +use std::marker::PhantomData; + pub use linkme; use crate::{ - context::{Cx, ModuleContext}, + context::{Context, Cx, ModuleContext}, handle::Handle, result::{JsResult, NeonResult}, types::{extract::TryIntoJs, JsValue}, }; +#[cfg(feature = "serde")] +use crate::types::extract::Json; + #[cfg(all(feature = "napi-6", feature = "futures"))] pub use self::futures::*; @@ -23,46 +28,86 @@ 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 Cx<'cx>) -> JsResult<'cx, JsValue>; +// Wrapper for the value type and return type tags +pub struct NeonMarker(PhantomData, PhantomData); + +// Markers to determine the type of a value +#[cfg(feature = "serde")] +pub struct NeonJsonTag; +pub struct NeonValueTag; +pub struct NeonResultTag; + +pub trait ToNeonMarker { + type Return; + + fn to_neon_marker(&self) -> NeonMarker; } -impl<'cx, T> NeonExportReturnValue<'cx> for T -where - T: TryIntoJs<'cx>, -{ - fn try_neon_export_return(self, cx: &mut Cx<'cx>) -> JsResult<'cx, JsValue> { - self.try_into_js(cx).map(|v| v.upcast()) +// Specialized implementation for `Result` +impl ToNeonMarker for Result { + type Return = NeonResultTag; + + fn to_neon_marker(&self) -> NeonMarker { + NeonMarker(PhantomData, PhantomData) } } -#[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 Cx<'cx>) -> JsResult<'cx, JsValue>; +// Default implementation that takes lower precedence due to autoref +impl ToNeonMarker for &T { + type Return = NeonValueTag; + + fn to_neon_marker(&self) -> NeonMarker { + NeonMarker(PhantomData, PhantomData) + } +} + +impl NeonMarker { + pub fn neon_into_js<'cx, T>(self, cx: &mut Cx<'cx>, v: T) -> JsResult<'cx, JsValue> + where + T: TryIntoJs<'cx>, + { + v.try_into_js(cx).map(|v| v.upcast()) + } } #[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 Cx<'cx>) -> JsResult<'cx, JsValue> { - self.map(crate::types::extract::Json).try_into_js(cx) +impl NeonMarker { + pub fn neon_into_js<'cx, T>(self, cx: &mut Cx<'cx>, v: T) -> JsResult<'cx, JsValue> + where + Json: TryIntoJs<'cx>, + { + Json(v).try_into_js(cx).map(|v| v.upcast()) } } #[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 Cx<'cx>) -> JsResult<'cx, JsValue> { - crate::types::extract::Json(self).try_into_js(cx) +impl NeonMarker { + pub fn neon_into_js<'cx, T, E>( + self, + cx: &mut Cx<'cx>, + res: Result, + ) -> JsResult<'cx, JsValue> + where + Result, E>: TryIntoJs<'cx>, + { + res.map(Json).try_into_js(cx).map(|v| v.upcast()) + } +} + +impl NeonMarker { + pub fn into_neon_result(self, _cx: &mut Cx, v: T) -> NeonResult { + Ok(v) + } +} + +impl NeonMarker { + pub fn into_neon_result<'cx, T, E>(self, cx: &mut Cx<'cx>, res: Result) -> NeonResult + where + E: TryIntoJs<'cx>, + { + match res { + Ok(v) => Ok(v), + Err(err) => err.try_into_js(cx).and_then(|err| cx.throw(err)), + } } } diff --git a/test/napi/src/js/futures.rs b/test/napi/src/js/futures.rs index 7e401f300..a9acb50a3 100644 --- a/test/napi/src/js/futures.rs +++ b/test/napi/src/js/futures.rs @@ -2,7 +2,10 @@ use std::future::Future; use neon::{ prelude::*, - types::{buffer::TypedArray, extract::Error}, + types::{ + buffer::TypedArray, + extract::{Error, Json, With, TryIntoJs}, + }, }; use crate::runtime; @@ -107,3 +110,28 @@ fn async_div(cx: &mut FunctionContext) -> NeonResult>, +) -> NeonResult TryIntoJs<'cx>>> { + fn emit(cx: &mut Cx, state: &str) -> NeonResult<()> { + cx.global::("process")? + .call_method_with(cx, "emit")? + .arg(cx.string("async_kitchen_sink")) + .arg(cx.string(state)) + .exec(cx) + } + + emit(cx, "start")?; + + Ok(async move { + let res = data.into_iter().map(|(a, b)| a * b).collect::>(); + + With(move |cx| -> NeonResult<_> { + emit(cx, "end")?; + Ok(res) + }) + }) +}