support async functions
This commit is contained in:
parent
cf0b5785cd
commit
0ee80662be
15 changed files with 138 additions and 66 deletions
|
@ -7,7 +7,8 @@ pub struct NapiFn {
|
||||||
pub js_name: String,
|
pub js_name: String,
|
||||||
pub attrs: Vec<Attribute>,
|
pub attrs: Vec<Attribute>,
|
||||||
pub args: Vec<NapiFnArgKind>,
|
pub args: Vec<NapiFnArgKind>,
|
||||||
pub ret: Option<(syn::Type, /* is_result */ bool)>,
|
pub ret: Option<syn::Type>,
|
||||||
|
pub is_ret_result: bool,
|
||||||
pub is_async: bool,
|
pub is_async: bool,
|
||||||
pub fn_self: Option<FnSelf>,
|
pub fn_self: Option<FnSelf>,
|
||||||
pub kind: FnKind,
|
pub kind: FnKind,
|
||||||
|
|
|
@ -17,27 +17,39 @@ impl TryToTokens for NapiFn {
|
||||||
let receiver_ret_name = Ident::new("_ret", Span::call_site());
|
let receiver_ret_name = Ident::new("_ret", Span::call_site());
|
||||||
let ret = self.gen_fn_return(&receiver_ret_name);
|
let ret = self.gen_fn_return(&receiver_ret_name);
|
||||||
let register = self.gen_fn_register();
|
let register = self.gen_fn_register();
|
||||||
|
|
||||||
let attrs = &self.attrs;
|
let attrs = &self.attrs;
|
||||||
let function_call_tokens = if args_len == 0 && self.fn_self.is_none() {
|
|
||||||
|
let native_call = if !self.is_async {
|
||||||
quote! {
|
quote! {
|
||||||
{
|
let #receiver_ret_name = {
|
||||||
let #receiver_ret_name = #receiver();
|
#receiver(#(#arg_names),*)
|
||||||
#ret
|
};
|
||||||
}
|
#ret
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let call = if self.is_ret_result {
|
||||||
|
quote! { #receiver(#(#arg_names),*).await }
|
||||||
|
} else {
|
||||||
|
quote! { Ok(#receiver(#(#arg_names),*).await) }
|
||||||
|
};
|
||||||
quote! {
|
quote! {
|
||||||
CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| {
|
execute_tokio_future(env, async { #call }, |env, #receiver_ret_name| {
|
||||||
#(#arg_conversions)*
|
|
||||||
let #receiver_ret_name = {
|
|
||||||
#receiver(#(#arg_names),*)
|
|
||||||
};
|
|
||||||
|
|
||||||
#ret
|
#ret
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let function_call = if args_len == 0 && self.fn_self.is_none() {
|
||||||
|
quote! { #native_call }
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| {
|
||||||
|
#(#arg_conversions)*
|
||||||
|
#native_call
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(quote! {
|
(quote! {
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -48,7 +60,7 @@ impl TryToTokens for NapiFn {
|
||||||
cb: sys::napi_callback_info
|
cb: sys::napi_callback_info
|
||||||
) -> sys::napi_value {
|
) -> sys::napi_value {
|
||||||
unsafe {
|
unsafe {
|
||||||
#function_call_tokens.unwrap_or_else(|e| {
|
#function_call.unwrap_or_else(|e| {
|
||||||
JsError::from(e).throw_into(env);
|
JsError::from(e).throw_into(env);
|
||||||
std::ptr::null_mut::<sys::napi_value__>()
|
std::ptr::null_mut::<sys::napi_value__>()
|
||||||
})
|
})
|
||||||
|
@ -120,12 +132,12 @@ impl NapiFn {
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
quote! {
|
quote! {
|
||||||
let #arg_name = unsafe { <#elem as FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))? };
|
let #arg_name = <#elem as FromNapiMutRef>::from_napi_mut_ref(env, cb.get_arg(#index))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
|
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
|
||||||
quote! {
|
quote! {
|
||||||
let #arg_name = unsafe { <#elem as FromNapiRef>::from_napi_ref(env, cb.get_arg(#index))? };
|
let #arg_name = <#elem as FromNapiRef>::from_napi_ref(env, cb.get_arg(#index))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -138,7 +150,7 @@ impl NapiFn {
|
||||||
};
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
let #arg_name = unsafe {
|
let #arg_name = {
|
||||||
#type_check
|
#type_check
|
||||||
<#ty as FromNapiValue>::from_napi_value(env, cb.get_arg(#index))?
|
<#ty as FromNapiValue>::from_napi_value(env, cb.get_arg(#index))?
|
||||||
};
|
};
|
||||||
|
@ -212,18 +224,23 @@ impl NapiFn {
|
||||||
|
|
||||||
fn gen_fn_return(&self, ret: &Ident) -> TokenStream {
|
fn gen_fn_return(&self, ret: &Ident) -> TokenStream {
|
||||||
let js_name = &self.js_name;
|
let js_name = &self.js_name;
|
||||||
let ret_ty = &self.ret;
|
|
||||||
|
|
||||||
if let Some((ref ty, is_result)) = ret_ty {
|
if let Some(ty) = &self.ret {
|
||||||
if self.kind == FnKind::Constructor {
|
if self.kind == FnKind::Constructor {
|
||||||
quote! { cb.construct(#js_name, #ret) }
|
quote! { cb.construct(#js_name, #ret) }
|
||||||
} else if *is_result {
|
} else if self.is_ret_result {
|
||||||
quote! {
|
if self.is_async {
|
||||||
if #ret.is_ok() {
|
quote! {
|
||||||
<#ty as ToNapiValue>::to_napi_value(env, #ret)
|
<#ty as ToNapiValue>::to_napi_value(env, #ret)
|
||||||
} else {
|
}
|
||||||
JsError::from(#ret.unwrap_err()).throw_into(env);
|
} else {
|
||||||
Ok(std::ptr::null_mut())
|
quote! {
|
||||||
|
if #ret.is_ok() {
|
||||||
|
<Result<#ty> as ToNapiValue>::to_napi_value(env, #ret)
|
||||||
|
} else {
|
||||||
|
JsError::from(#ret.unwrap_err()).throw_into(env);
|
||||||
|
Ok(std::ptr::null_mut())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -54,6 +54,8 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||||
("Value", "any"),
|
("Value", "any"),
|
||||||
("Map", "Record<string, any>"),
|
("Map", "Record<string, any>"),
|
||||||
("HashMap", "Record<{}, {}>"),
|
("HashMap", "Record<{}, {}>"),
|
||||||
|
("Buffer", "Buffer"),
|
||||||
|
// TODO: Vec<u8> should be Buffer, now is Array<number>
|
||||||
("Vec", "Array<{}>"),
|
("Vec", "Array<{}>"),
|
||||||
("Option", "{} | null"),
|
("Option", "{} | null"),
|
||||||
("Result", "Error | {}"),
|
("Result", "Error | {}"),
|
||||||
|
|
|
@ -87,8 +87,8 @@ impl NapiFn {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
FnKind::Constructor | FnKind::Setter => "".to_owned(),
|
FnKind::Constructor | FnKind::Setter => "".to_owned(),
|
||||||
_ => {
|
_ => {
|
||||||
let ret = if let Some((ref ret, is_result)) = self.ret {
|
let ret = if let Some(ret) = &self.ret {
|
||||||
let ts_type = ty_to_ts_type(ret, is_result);
|
let ts_type = ty_to_ts_type(ret, true);
|
||||||
if ts_type == "undefined" {
|
if ts_type == "undefined" {
|
||||||
"void".to_owned()
|
"void".to_owned()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -194,7 +194,7 @@ fn extract_path_ident(path: &syn::Path) -> BindgenResult<Ident> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_fn_types(
|
fn extract_callback_trait_types(
|
||||||
arguments: &syn::PathArguments,
|
arguments: &syn::PathArguments,
|
||||||
) -> BindgenResult<(Vec<syn::Type>, Option<syn::Type>)> {
|
) -> BindgenResult<(Vec<syn::Type>, Option<syn::Type>)> {
|
||||||
match arguments {
|
match arguments {
|
||||||
|
@ -209,39 +209,14 @@ fn extract_fn_types(
|
||||||
let ret = match &arguments.output {
|
let ret = match &arguments.output {
|
||||||
syn::ReturnType::Type(_, ret_ty) => {
|
syn::ReturnType::Type(_, ret_ty) => {
|
||||||
let ret_ty = &**ret_ty;
|
let ret_ty = &**ret_ty;
|
||||||
match ret_ty {
|
if let Some(ty_of_result) = extract_result_ty(ret_ty)? {
|
||||||
syn::Type::Path(syn::TypePath {
|
if ty_of_result.to_token_stream().to_string() == "()" {
|
||||||
qself: None,
|
None
|
||||||
ref path,
|
} else {
|
||||||
}) if path.segments.len() == 1 => {
|
Some(ty_of_result)
|
||||||
let segment = path.segments.first().unwrap();
|
|
||||||
|
|
||||||
if segment.ident != "Result" {
|
|
||||||
bail_span!(ret_ty, "The return type of callback can only be `Result`");
|
|
||||||
} else {
|
|
||||||
match &segment.arguments {
|
|
||||||
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
|
||||||
args,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
// fast test
|
|
||||||
if args.to_token_stream().to_string() == "()" {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let ok_arg = args.first().unwrap();
|
|
||||||
match ok_arg {
|
|
||||||
syn::GenericArgument::Type(ty) => Some(ty.clone()),
|
|
||||||
_ => bail_span!(ok_arg, "unsupported generic type"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
bail_span!(segment, "Too many arguments")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => bail_span!(ret_ty, "The return type of callback can only be `Result`"),
|
} else {
|
||||||
|
bail_span!(ret_ty, "The return type of callback can only be `Result`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -257,6 +232,34 @@ fn extract_fn_types(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_result_ty(ty: &syn::Type) -> BindgenResult<Option<syn::Type>> {
|
||||||
|
match ty {
|
||||||
|
syn::Type::Path(syn::TypePath { qself: None, path }) if path.segments.len() == 1 => {
|
||||||
|
let segment = path.segments.first().unwrap();
|
||||||
|
|
||||||
|
if segment.ident != "Result" {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
match &segment.arguments {
|
||||||
|
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
||||||
|
args, ..
|
||||||
|
}) => {
|
||||||
|
let ok_arg = args.first().unwrap();
|
||||||
|
match ok_arg {
|
||||||
|
syn::GenericArgument::Type(ty) => Ok(Some(ty.clone())),
|
||||||
|
_ => bail_span!(ok_arg, "unsupported generic type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
bail_span!(segment, "unsupported generic type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
|
fn get_expr(mut expr: &syn::Expr) -> &syn::Expr {
|
||||||
while let syn::Expr::Group(g) = expr {
|
while let syn::Expr::Group(g) = expr {
|
||||||
expr = &g.expr;
|
expr = &g.expr;
|
||||||
|
@ -480,7 +483,7 @@ fn napi_fn_from_decl(
|
||||||
syn::FnArg::Typed(mut p) => {
|
syn::FnArg::Typed(mut p) => {
|
||||||
let ty_str = p.ty.to_token_stream().to_string();
|
let ty_str = p.ty.to_token_stream().to_string();
|
||||||
if let Some(path_arguments) = callback_traits.get(&ty_str) {
|
if let Some(path_arguments) = callback_traits.get(&ty_str) {
|
||||||
match extract_fn_types(path_arguments) {
|
match extract_callback_trait_types(path_arguments) {
|
||||||
Ok((fn_args, fn_ret)) => Some(NapiFnArgKind::Callback(Box::new(CallbackArg {
|
Ok((fn_args, fn_ret)) => Some(NapiFnArgKind::Callback(Box::new(CallbackArg {
|
||||||
pat: p.pat,
|
pat: p.pat,
|
||||||
args: fn_args,
|
args: fn_args,
|
||||||
|
@ -518,11 +521,15 @@ fn napi_fn_from_decl(
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let ret = match output {
|
let (ret, is_ret_result) = match output {
|
||||||
syn::ReturnType::Default => None,
|
syn::ReturnType::Default => (None, false),
|
||||||
syn::ReturnType::Type(_, ty) => {
|
syn::ReturnType::Type(_, ty) => {
|
||||||
let is_result = ty.to_token_stream().to_string().starts_with("Result <");
|
let result_ty = extract_result_ty(&ty)?;
|
||||||
Some((replace_self(*ty, parent), is_result))
|
if result_ty.is_some() {
|
||||||
|
(result_ty, true)
|
||||||
|
} else {
|
||||||
|
(Some(replace_self(*ty, parent)), false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -559,6 +566,7 @@ fn napi_fn_from_decl(
|
||||||
js_name,
|
js_name,
|
||||||
args,
|
args,
|
||||||
ret,
|
ret,
|
||||||
|
is_ret_result,
|
||||||
is_async: asyncness.is_some(),
|
is_async: asyncness.is_some(),
|
||||||
vis,
|
vis,
|
||||||
kind: fn_kind(opts),
|
kind: fn_kind(opts),
|
||||||
|
|
|
@ -23,6 +23,8 @@ napi7 = ["napi6", "napi-sys/napi7"]
|
||||||
napi8 = ["napi7", "napi-sys/napi8"]
|
napi8 = ["napi7", "napi-sys/napi8"]
|
||||||
serde-json = ["serde", "serde_json"]
|
serde-json = ["serde", "serde_json"]
|
||||||
tokio_rt = ["tokio", "once_cell", "napi4"]
|
tokio_rt = ["tokio", "once_cell", "napi4"]
|
||||||
|
async = ["tokio_rt"]
|
||||||
|
full = ["latin1", "napi8", "async", "serde-json"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
|
|
|
@ -103,3 +103,9 @@ impl ValidateNapiValue for Buffer {
|
||||||
vec![ValueType::Object]
|
vec![ValueType::Object]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToNapiValue for Vec<u8> {
|
||||||
|
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||||
|
Buffer::to_napi_value(env, val.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -158,6 +158,7 @@ macro_rules! assert_type_of {
|
||||||
pub mod bindgen_prelude {
|
pub mod bindgen_prelude {
|
||||||
#[cfg(feature = "compat-mode")]
|
#[cfg(feature = "compat-mode")]
|
||||||
pub use crate::bindgen_runtime::register_module_exports;
|
pub use crate::bindgen_runtime::register_module_exports;
|
||||||
|
pub use crate::tokio_runtime::*;
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
|
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
|
||||||
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
|
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
|
||||||
|
|
|
@ -13,10 +13,12 @@ napi3 = ["napi/napi3"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
napi-derive = { path = "../../crates/macro", features = ["type-def"] }
|
napi-derive = { path = "../../crates/macro", features = ["type-def"] }
|
||||||
napi = { path = "../../crates/napi", features = ["latin1", "serde-json"] }
|
napi = { path = "../../crates/napi", features = ["full"] }
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
tokio = {version = "1", features = ["default", "fs"]}
|
||||||
|
futures = "0.3"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = { path = "../../crates/build" }
|
napi-build = { path = "../../crates/build" }
|
|
@ -11,6 +11,7 @@ Generated by [AVA](https://avajs.dev).
|
||||||
`export function getWords(): Array<string>␊
|
`export function getWords(): Array<string>␊
|
||||||
export function getNums(): Array<number>␊
|
export function getNums(): Array<number>␊
|
||||||
export function sumNums(nums: Array<number>): number␊
|
export function sumNums(nums: Array<number>): number␊
|
||||||
|
export function readFileAsync(path: string): Promise<Array<number>>␊
|
||||||
export function getCwd(callback: (arg0: string) => void): void␊
|
export function getCwd(callback: (arg0: string) => void): void␊
|
||||||
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void␊
|
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void␊
|
||||||
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }␊
|
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }␊
|
||||||
|
|
Binary file not shown.
|
@ -1,3 +1,5 @@
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
import test from 'ava'
|
import test from 'ava'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -23,6 +25,7 @@ import {
|
||||||
readPackageJson,
|
readPackageJson,
|
||||||
getPackageJsonName,
|
getPackageJsonName,
|
||||||
getBuffer,
|
getBuffer,
|
||||||
|
readFileAsync,
|
||||||
} from '../'
|
} from '../'
|
||||||
|
|
||||||
test('number', (t) => {
|
test('number', (t) => {
|
||||||
|
@ -117,3 +120,13 @@ test('serde-json', (t) => {
|
||||||
test('buffer', (t) => {
|
test('buffer', (t) => {
|
||||||
t.is(getBuffer().toString('utf-8'), 'Hello world')
|
t.is(getBuffer().toString('utf-8'), 'Hello world')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('async', async (t) => {
|
||||||
|
const bufPromise = readFileAsync(join(__dirname, '../package.json'))
|
||||||
|
await t.notThrowsAsync(bufPromise)
|
||||||
|
const buf = await bufPromise
|
||||||
|
const { name } = JSON.parse(buf.toString())
|
||||||
|
t.is(name, 'napi-examples')
|
||||||
|
|
||||||
|
await t.throwsAsync(() => readFileAsync('some_nonexist_path.file'))
|
||||||
|
})
|
||||||
|
|
1
examples/napi/index.d.ts
vendored
1
examples/napi/index.d.ts
vendored
|
@ -1,6 +1,7 @@
|
||||||
export function getWords(): Array<string>
|
export function getWords(): Array<string>
|
||||||
export function getNums(): Array<number>
|
export function getNums(): Array<number>
|
||||||
export function sumNums(nums: Array<number>): number
|
export function sumNums(nums: Array<number>): number
|
||||||
|
export function readFileAsync(path: string): Promise<Array<number>>
|
||||||
export function getCwd(callback: (arg0: string) => void): void
|
export function getCwd(callback: (arg0: string) => void): void
|
||||||
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void
|
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void
|
||||||
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }
|
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }
|
||||||
|
|
17
examples/napi/src/async.rs
Normal file
17
examples/napi/src/async.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use futures::prelude::*;
|
||||||
|
use napi::bindgen_prelude::*;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
async fn read_file_async(path: String) -> Result<Vec<u8>> {
|
||||||
|
fs::read(path)
|
||||||
|
.map(|v| {
|
||||||
|
v.map_err(|e| {
|
||||||
|
Error::new(
|
||||||
|
Status::GenericFailure,
|
||||||
|
format!("failed to read file, {}", e),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ extern crate napi_derive;
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
mod array;
|
mod array;
|
||||||
|
mod r#async;
|
||||||
mod callback;
|
mod callback;
|
||||||
mod class;
|
mod class;
|
||||||
mod r#enum;
|
mod r#enum;
|
||||||
|
|
Loading…
Reference in a new issue