Skip to content

Commit

Permalink
Simplify macro markers and add some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kjvalencik committed Sep 10, 2024
1 parent 5b25579 commit 394b695
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 84 deletions.
16 changes: 8 additions & 8 deletions crates/neon-macros/src/export/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<NeonReturnTag>().neon_into_js(&mut cx, res)
});

// Default export name as identity unless a name is provided
Expand All @@ -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::<NeonValueTag>().into_neon_result(&mut cx, fut)?
};

neon::macro_internal::spawn(&mut cx, fut, |mut cx, res| #result_extract)
Expand Down
47 changes: 2 additions & 45 deletions crates/neon/src/macro_internal/futures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -27,46 +27,3 @@ where

Ok(promise.upcast())
}

pub trait ToNeonFutureMarker {
type Marker;

fn to_neon_future_marker(&self) -> Self::Marker;
}

impl<T, E> ToNeonFutureMarker for Result<T, E> {
type Marker = NeonFutureMarkerResult;

fn to_neon_future_marker(&self) -> Self::Marker {
NeonFutureMarkerResult
}
}

impl<T> 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<T, E>) -> NeonResult<T>
where
E: TryIntoJs<'cx>,
{
res.or_else(|err| {
let err = err.try_into_js(cx)?;
cx.throw(err)
})
}
}

impl NeonFutureMarkerValue {
pub fn make_result<T>(self, _cx: &mut Cx, res: T) -> NeonResult<T> {
Ok(res)
}
}
105 changes: 75 additions & 30 deletions crates/neon/src/macro_internal/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;

Expand All @@ -23,46 +28,86 @@ pub static EXPORTS: [for<'cx> fn(&mut ModuleContext<'cx>) -> NeonResult<Export<'
#[linkme::distributed_slice]
pub static MAIN: [for<'cx> 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<Tag, Return>(PhantomData<Tag>, PhantomData<Return>);

// 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<Tag>(&self) -> NeonMarker<Tag, Self::Return>;
}

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<T, E> ToNeonMarker for Result<T, E> {
type Return = NeonResultTag;

fn to_neon_marker<Tag>(&self) -> NeonMarker<Tag, Self::Return> {
NeonMarker(PhantomData, PhantomData)
}
}

#[cfg(feature = "serde")]
// Trait used for specializing `Json` wrapping of `T` or `Result<T, _>` 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<T> ToNeonMarker for &T {
type Return = NeonValueTag;

fn to_neon_marker<Tag>(&self) -> NeonMarker<Tag, Self::Return> {
NeonMarker(PhantomData, PhantomData)
}
}

impl<Return> NeonMarker<NeonValueTag, Return> {
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<T, E>
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<NeonJsonTag, NeonValueTag> {
pub fn neon_into_js<'cx, T>(self, cx: &mut Cx<'cx>, v: T) -> JsResult<'cx, JsValue>
where
Json<T>: 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<NeonJsonTag, NeonResultTag> {
pub fn neon_into_js<'cx, T, E>(
self,
cx: &mut Cx<'cx>,
res: Result<T, E>,
) -> JsResult<'cx, JsValue>
where
Result<Json<T>, E>: TryIntoJs<'cx>,
{
res.map(Json).try_into_js(cx).map(|v| v.upcast())
}
}

impl<Tag> NeonMarker<Tag, NeonValueTag> {
pub fn into_neon_result<T>(self, _cx: &mut Cx, v: T) -> NeonResult<T> {
Ok(v)
}
}

impl<Tag> NeonMarker<Tag, NeonResultTag> {
pub fn into_neon_result<'cx, T, E>(self, cx: &mut Cx<'cx>, res: Result<T, E>) -> NeonResult<T>
where
E: TryIntoJs<'cx>,
{
match res {
Ok(v) => Ok(v),
Err(err) => err.try_into_js(cx).and_then(|err| cx.throw(err)),
}
}
}
30 changes: 29 additions & 1 deletion test/napi/src/js/futures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::future::Future;

use neon::{
prelude::*,
types::{buffer::TypedArray, extract::Error},
types::{
buffer::TypedArray,
extract::{Error, Json, TryIntoJs, With},
},
};

use crate::runtime;
Expand Down Expand Up @@ -107,3 +110,28 @@ fn async_div(cx: &mut FunctionContext) -> NeonResult<impl Future<Output = Result
Ok(a / b)
})
}

#[neon::export(async)]
fn async_with_events(
cx: &mut FunctionContext,
Json(data): Json<Vec<(f64, f64)>>,
) -> NeonResult<impl Future<Output = impl for<'cx> TryIntoJs<'cx>>> {
fn emit(cx: &mut Cx, state: &str) -> NeonResult<()> {
cx.global::<JsObject>("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::<Vec<_>>();

With(move |cx| -> NeonResult<_> {
emit(cx, "end")?;
Ok(res)
})
})
}

0 comments on commit 394b695

Please sign in to comment.