Skip to content

Commit

Permalink
Merge pull request #1057 from neon-bindings/kv/autoref
Browse files Browse the repository at this point in the history
improve(neon): Use autoref specialization for JSON wrapping return values in exported functions
  • Loading branch information
kjvalencik authored Jul 23, 2024
2 parents 922e93a + 5b6f345 commit 37e43cc
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 73 deletions.
11 changes: 0 additions & 11 deletions crates/neon-macros/src/export/function/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pub(crate) struct Meta {
pub(super) name: Option<syn::LitStr>,
pub(super) json: bool,
pub(super) context: bool,
pub(super) result: bool,
}

#[derive(Default)]
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
}
Expand Down
57 changes: 16 additions & 41 deletions crates/neon-macros/src/export/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
),
Expand Down Expand Up @@ -166,28 +166,3 @@ fn has_context_arg(meta: &meta::Meta, sig: &syn::Signature) -> syn::Result<bool>

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"
}
64 changes: 63 additions & 1 deletion crates/neon/src/macro_internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>);

Expand All @@ -11,3 +16,60 @@ 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<C>(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<C>(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<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<C>(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<T, E>
where
T: serde::Serialize,
E: TryIntoJs<'cx>,
{
fn try_neon_export_return<C>(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<C>(self, cx: &mut C) -> JsResult<'cx, JsValue>
where
C: Context<'cx>,
{
crate::types::extract::Json(self).try_into_js(cx)
}
}
17 changes: 0 additions & 17 deletions crates/neon/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<f64> {
/// Ok(a + b)
/// }
/// ```
pub use neon_macros::export;
3 changes: 0 additions & 3 deletions crates/neon/src/sys/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,3 @@ impl Default for EscapableHandleScope {
Self::new()
}
}

#[derive(Clone, Copy)]
pub struct InheritedHandleScope;

0 comments on commit 37e43cc

Please sign in to comment.