feat(napi): convert ToNapiValue tuple to variadic tsfn (#1475)

* refactor: convert ToNapiValue tuple to variadic tsfn

* chore: resolve conflicts

* fix: typo

* chore: use into instead of to

* chore: syntax compat
This commit is contained in:
Hana 2023-02-08 22:30:12 +08:00 committed by LongYinan
parent a7dcf2a838
commit 90cc0a6abe
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
10 changed files with 202 additions and 53 deletions

View file

@ -218,6 +218,12 @@ fn is_ts_union_type(rust_ty: &str) -> bool {
.unwrap_or(false) .unwrap_or(false)
} }
const TSFN_RUST_TY: &str = "ThreadsafeFunction";
fn should_convert_tuple_to_variadic(rust_ty: &str) -> bool {
rust_ty == TSFN_RUST_TY
}
fn is_ts_function_type_notation(ty: &Type) -> bool { fn is_ts_function_type_notation(ty: &Type) -> bool {
match ty { match ty {
Type::Path(syn::TypePath { qself: None, path }) => { Type::Path(syn::TypePath { qself: None, path }) => {
@ -235,12 +241,32 @@ fn is_ts_function_type_notation(ty: &Type) -> bool {
} }
} }
pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (String, bool) { pub fn ty_to_ts_type(
ty: &Type,
is_return_ty: bool,
is_struct_field: bool,
convert_tuple_to_variadic: bool,
) -> (String, bool, bool) {
match ty { match ty {
Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty, is_struct_field), Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty, is_struct_field, false),
Type::Tuple(tuple) => { Type::Tuple(tuple) => {
if tuple.elems.is_empty() { if tuple.elems.is_empty() {
("undefined".to_owned(), false) ("undefined".to_owned(), false, false)
} else if convert_tuple_to_variadic {
let variadic = &tuple
.elems
.iter()
.enumerate()
.map(|(i, arg)| {
let (ts_type, is_optional, _) = ty_to_ts_type(arg, false, false, false);
r#fn::FnArg {
arg: format!("arg{}", i),
ts_type,
is_optional,
}
})
.collect::<r#fn::FnArgList>();
(format!("{}", variadic), false, true)
} else { } else {
( (
format!( format!(
@ -248,11 +274,12 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
tuple tuple
.elems .elems
.iter() .iter()
.map(|elem| ty_to_ts_type(elem, false, false).0) .map(|elem| ty_to_ts_type(elem, false, false, false).0)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", ") .join(", ")
), ),
false, false,
false,
) )
} }
} }
@ -267,14 +294,18 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
.args .args
.iter() .iter()
.filter_map(|arg| match arg { .filter_map(|arg| match arg {
syn::GenericArgument::Type(generic_ty) => { syn::GenericArgument::Type(generic_ty) => Some(ty_to_ts_type(
Some(ty_to_ts_type(generic_ty, false, false)).map(|(mut ty, is_struct_field)| { generic_ty,
if is_ts_union_type && is_ts_function_type_notation(generic_ty) { false,
ty = format!("({})", ty); false,
} should_convert_tuple_to_variadic(&rust_ty),
(ty, is_struct_field) ))
}) .map(|(mut ty, is_optional, is_variadic)| {
} if is_ts_union_type && is_ts_function_type_notation(generic_ty) {
ty = format!("({})", ty);
}
(ty, is_optional, is_variadic)
}),
_ => None, _ => None,
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -285,7 +316,7 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
if rust_ty == "Result" && is_return_ty { if rust_ty == "Result" && is_return_ty {
ts_ty = Some(args.first().unwrap().to_owned()); ts_ty = Some(args.first().unwrap().to_owned());
} else if rust_ty == "Option" { } else if rust_ty == "Option" {
ts_ty = args.first().map(|(arg, _)| { ts_ty = args.first().map(|(arg, _, _)| {
( (
if is_struct_field { if is_struct_field {
arg.to_string() arg.to_string()
@ -295,56 +326,78 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
format!("{} | undefined | null", arg) format!("{} | undefined | null", arg)
}, },
true, true,
false,
) )
}); });
} else if rust_ty == "AsyncTask" { } else if rust_ty == "AsyncTask" {
ts_ty = r#struct::TASK_STRUCTS.with(|t| { ts_ty = r#struct::TASK_STRUCTS.with(|t| {
let (output_type, _) = args.first().unwrap().to_owned(); let (output_type, _, _) = args.first().unwrap().to_owned();
if let Some(o) = t.borrow().get(&output_type) { if let Some(o) = t.borrow().get(&output_type) {
Some((format!("Promise<{}>", o), false)) Some((format!("Promise<{}>", o), false, false))
} else { } else {
Some(("Promise<unknown>".to_owned(), false)) Some(("Promise<unknown>".to_owned(), false, false))
} }
}); });
} else if rust_ty == "Reference" || rust_ty == "WeakReference" { } else if rust_ty == "Reference" || rust_ty == "WeakReference" {
ts_ty = r#struct::TASK_STRUCTS.with(|t| { ts_ty = r#struct::TASK_STRUCTS.with(|t| {
// Reference<T> => T // Reference<T> => T
if let Some(arg) = args.first() { if let Some(arg) = args.first() {
let (output_type, _) = arg.to_owned(); let (output_type, _, _) = arg.to_owned();
if let Some(o) = t.borrow().get(&output_type) { if let Some(o) = t.borrow().get(&output_type) {
Some((o.to_owned(), false)) Some((o.to_owned(), false, false))
} else { } else {
Some((output_type, false)) Some((output_type, false, false))
} }
} else { } else {
// Not NAPI-RS `Reference` // Not NAPI-RS `Reference`
Some((rust_ty, false)) Some((rust_ty, false, false))
} }
}); });
} else if let Some(&(known_ty, _, _)) = KNOWN_TYPES.get(rust_ty.as_str()) { } else if let Some(&(known_ty, _, _)) = KNOWN_TYPES.get(rust_ty.as_str()) {
if known_ty.contains("{}") { if known_ty.contains("{}") {
ts_ty = Some(( ts_ty = Some((
fill_ty(known_ty, args.into_iter().map(|(arg, _)| arg).collect()), fill_ty(known_ty, args.into_iter().map(|(arg, _, _)| arg).collect()),
false,
false, false,
)); ));
} else { } else {
ts_ty = Some((known_ty.to_owned(), false)); ts_ty = Some((known_ty.to_owned(), false, false));
} }
} else if let Some(t) = crate::typegen::r#struct::CLASS_STRUCTS } else if let Some(t) = crate::typegen::r#struct::CLASS_STRUCTS
.with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned()) .with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned())
{ {
ts_ty = Some((t, false)); ts_ty = Some((t, false, false));
} else if rust_ty == "ThreadsafeFunction" { } else if rust_ty == TSFN_RUST_TY {
let fatal_tsfn = match args.get(1) { let fatal_tsfn = match args.get(1) {
Some((arg, _)) => arg == "Fatal", Some((arg, _, _)) => arg == "Fatal",
_ => false, _ => false,
}; };
let first_arg = args.first().map(|(arg, _)| arg).unwrap(); let (args, is_variadic) = args
.first()
.map(|(arg, _, is_variadic)| (arg, is_variadic))
.unwrap();
ts_ty = if fatal_tsfn { ts_ty = if fatal_tsfn {
Some((format!("(value: {}) => any", first_arg), false)) Some((
{
if *is_variadic {
format!("({}) => any", args)
} else {
format!("(value: {}) => any", args)
}
},
false,
false,
))
} else { } else {
Some(( Some((
format!("(err: Error | null, value: {}) => any", first_arg), {
if *is_variadic {
format!("(err: Error | null, {}) => any", args)
} else {
format!("(err: Error | null, value: {}) => any", args)
}
},
false,
false, false,
)) ))
}; };
@ -354,19 +407,25 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
aliases aliases
.borrow() .borrow()
.get(rust_ty.as_str()) .get(rust_ty.as_str())
.map(|a| (a.to_owned(), false)) .map(|a| (a.to_owned(), false, false))
}); });
ts_ty = type_alias.or(Some((rust_ty, false))); ts_ty = type_alias.or(Some((rust_ty, false, false)));
} }
} }
ts_ty.unwrap_or_else(|| ("any".to_owned(), false)) ts_ty.unwrap_or_else(|| ("any".to_owned(), false, false))
} }
Type::Group(g) => ty_to_ts_type(&g.elem, is_return_ty, is_struct_field), Type::Group(g) => ty_to_ts_type(&g.elem, is_return_ty, is_struct_field, false),
Type::Array(a) => { Type::Array(a) => {
let (element_type, is_optional) = ty_to_ts_type(&a.elem, is_return_ty, is_struct_field); let (element_type, is_optional, _) =
(format!("{}[]", element_type), is_optional) ty_to_ts_type(&a.elem, is_return_ty, is_struct_field, false);
(format!("{}[]", element_type), is_optional, false)
} }
_ => ("any".to_owned(), false), Type::Paren(p) => {
let (element_type, is_optional, _) =
ty_to_ts_type(&p.elem, is_return_ty, is_struct_field, false);
(element_type, is_optional, false)
}
_ => ("any".to_owned(), false, false),
} }
} }

View file

@ -17,7 +17,7 @@ impl ToTypeDef for NapiConst {
def: format!( def: format!(
"export const {}: {}", "export const {}: {}",
&self.js_name, &self.js_name,
ty_to_ts_type(&self.type_name, false, false).0 ty_to_ts_type(&self.type_name, false, false, false).0
), ),
js_mod: self.js_mod.to_owned(), js_mod: self.js_mod.to_owned(),
js_doc: js_doc_from_comments(&self.comments), js_doc: js_doc_from_comments(&self.comments),

View file

@ -6,13 +6,13 @@ use syn::{Pat, PathArguments, PathSegment};
use super::{ty_to_ts_type, ToTypeDef, TypeDef}; use super::{ty_to_ts_type, ToTypeDef, TypeDef};
use crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn}; use crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn};
struct FnArg { pub(crate) struct FnArg {
arg: String, pub(crate) arg: String,
ts_type: String, pub(crate) ts_type: String,
is_optional: bool, pub(crate) is_optional: bool,
} }
struct FnArgList { pub(crate) struct FnArgList {
this: Option<FnArg>, this: Option<FnArg>,
args: Vec<FnArg>, args: Vec<FnArg>,
last_required: Option<usize>, last_required: Option<usize>,
@ -110,7 +110,7 @@ fn gen_callback_type(callback: &CallbackArg) -> String {
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, arg)| { .map(|(i, arg)| {
let (ts_type, is_optional) = ty_to_ts_type(arg, false, false); let (ts_type, is_optional, _) = ty_to_ts_type(arg, false, false, false);
FnArg { FnArg {
arg: format!("arg{}", i), arg: format!("arg{}", i),
ts_type, ts_type,
@ -119,7 +119,7 @@ fn gen_callback_type(callback: &CallbackArg) -> String {
}) })
.collect::<FnArgList>(), .collect::<FnArgList>(),
ret = match &callback.ret { ret = match &callback.ret {
Some(ty) => ty_to_ts_type(ty, true, false).0, Some(ty) => ty_to_ts_type(ty, true, false, false).0,
None => "void".to_owned(), None => "void".to_owned(),
} }
) )
@ -153,7 +153,7 @@ impl NapiFn {
}) = arguments }) = arguments
{ {
if let Some(syn::GenericArgument::Type(ty)) = angle_bracketed_args.first() { if let Some(syn::GenericArgument::Type(ty)) = angle_bracketed_args.first() {
let (ts_type, _) = ty_to_ts_type(ty, false, false); let (ts_type, _, _) = ty_to_ts_type(ty, false, false, false);
return Some(FnArg { return Some(FnArg {
arg: "this".to_owned(), arg: "this".to_owned(),
ts_type, ts_type,
@ -178,7 +178,7 @@ impl NapiFn {
i.mutability = None; i.mutability = None;
} }
let (ts_type, is_optional) = ty_to_ts_type(&path.ty, false, false); let (ts_type, is_optional, _) = ty_to_ts_type(&path.ty, false, false, false);
let ts_type = arg.use_overridden_type_or(|| ts_type); let ts_type = arg.use_overridden_type_or(|| ts_type);
let arg = path.pat.to_token_stream().to_string().to_case(Case::Camel); let arg = path.pat.to_token_stream().to_string().to_case(Case::Camel);
@ -230,7 +230,7 @@ impl NapiFn {
.unwrap_or_else(|| "".to_owned()), .unwrap_or_else(|| "".to_owned()),
_ => { _ => {
let ret = if let Some(ret) = &self.ret { let ret = if let Some(ret) = &self.ret {
let (ts_type, _) = ty_to_ts_type(ret, true, false); let (ts_type, _, _) = ty_to_ts_type(ret, true, false, false);
if ts_type == "undefined" { if ts_type == "undefined" {
"void".to_owned() "void".to_owned()
} else if ts_type == "Self" { } else if ts_type == "Self" {

View file

@ -38,19 +38,19 @@ impl ToTypeDef for NapiImpl {
TASK_STRUCTS.with(|t| { TASK_STRUCTS.with(|t| {
t.borrow_mut().insert( t.borrow_mut().insert(
self.name.to_string(), self.name.to_string(),
ty_to_ts_type(output_type, false, true).0, ty_to_ts_type(output_type, false, true, false).0,
); );
}); });
} }
if let Some(output_type) = &self.iterator_yield_type { if let Some(output_type) = &self.iterator_yield_type {
let next_type = if let Some(ref ty) = self.iterator_next_type { let next_type = if let Some(ref ty) = self.iterator_next_type {
ty_to_ts_type(ty, false, false).0 ty_to_ts_type(ty, false, false, false).0
} else { } else {
"void".to_owned() "void".to_owned()
}; };
let return_type = if let Some(ref ty) = self.iterator_return_type { let return_type = if let Some(ref ty) = self.iterator_return_type {
ty_to_ts_type(ty, false, false).0 ty_to_ts_type(ty, false, false, false).0
} else { } else {
"void".to_owned() "void".to_owned()
}; };
@ -60,7 +60,7 @@ impl ToTypeDef for NapiImpl {
original_name: None, original_name: None,
def: format!( def: format!(
"[Symbol.iterator](): Iterator<{}, {}, {}>", "[Symbol.iterator](): Iterator<{}, {}, {}>",
ty_to_ts_type(output_type, false, true).0, ty_to_ts_type(output_type, false, true, false).0,
return_type, return_type,
next_type, next_type,
), ),
@ -118,7 +118,7 @@ impl NapiStruct {
field_str.push_str("readonly ") field_str.push_str("readonly ")
} }
let (arg, is_optional) = ty_to_ts_type(&f.ty, false, true); let (arg, is_optional, _) = ty_to_ts_type(&f.ty, false, true, false);
let arg = f.ts_type.as_ref().map(|ty| ty.to_string()).unwrap_or(arg); let arg = f.ts_type.as_ref().map(|ty| ty.to_string()).unwrap_or(arg);
let sep = if is_optional { "?" } else { "" }; let sep = if is_optional { "?" } else { "" };

View file

@ -208,9 +208,72 @@ impl<T: 'static, ES: ErrorStrategy::T> Clone for ThreadsafeFunction<T, ES> {
} }
} }
impl<T: ToNapiValue + 'static, ES: ErrorStrategy::T> FromNapiValue for ThreadsafeFunction<T, ES> { pub trait JsValuesTupleIntoVec {
fn into_vec(self, env: &Env) -> Result<Vec<JsUnknown>>;
}
impl<T: ToNapiValue> JsValuesTupleIntoVec for T {
fn into_vec(self, env: &Env) -> Result<Vec<JsUnknown>> {
Ok(vec![JsUnknown(crate::Value {
env: env.0,
value: unsafe { <T as ToNapiValue>::to_napi_value(env.0, self)? },
value_type: crate::ValueType::Unknown,
})])
}
}
macro_rules! impl_js_value_tuple_to_vec {
($($ident:ident),*) => {
impl<$($ident: ToNapiValue),*> JsValuesTupleIntoVec for ($($ident,)*) {
fn into_vec(self, env: &Env) -> Result<Vec<JsUnknown>> {
#[allow(non_snake_case)]
let ($($ident,)*) = self;
Ok(vec![$(JsUnknown($crate::Value {
env: env.0,
value: unsafe { <$ident as ToNapiValue>::to_napi_value(env.0, $ident)? },
value_type: $crate::ValueType::Unknown,
})),*])
}
}
};
}
impl_js_value_tuple_to_vec!(A);
impl_js_value_tuple_to_vec!(A, B);
impl_js_value_tuple_to_vec!(A, B, C);
impl_js_value_tuple_to_vec!(A, B, C, D);
impl_js_value_tuple_to_vec!(A, B, C, D, E);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
impl_js_value_tuple_to_vec!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y
);
impl_js_value_tuple_to_vec!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
);
impl<T: JsValuesTupleIntoVec + 'static, ES: ErrorStrategy::T> FromNapiValue
for ThreadsafeFunction<T, ES>
{
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
Self::create(env, napi_val, 0, |ctx| Ok(vec![ctx.value])) Self::create(env, napi_val, 0, |ctx| ctx.value.into_vec(&ctx.env))
} }
} }

View file

@ -209,6 +209,7 @@ Generated by [AVA](https://avajs.dev).
export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void> export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void>
export function acceptThreadsafeFunction(func: (err: Error | null, value: number) => any): void␊ export function acceptThreadsafeFunction(func: (err: Error | null, value: number) => any): void␊
export function acceptThreadsafeFunctionFatal(func: (value: number) => any): void␊ export function acceptThreadsafeFunctionFatal(func: (value: number) => any): void␊
export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void␊
export function getBuffer(): Buffer␊ export function getBuffer(): Buffer␊
export function appendBuffer(buf: Buffer): Buffer␊ export function appendBuffer(buf: Buffer): Buffer␊
export function getEmptyBuffer(): Buffer␊ export function getEmptyBuffer(): Buffer␊

View file

@ -119,6 +119,7 @@ import {
bigintFromI64, bigintFromI64,
acceptThreadsafeFunction, acceptThreadsafeFunction,
acceptThreadsafeFunctionFatal, acceptThreadsafeFunctionFatal,
acceptThreadsafeFunctionTupleArgs,
promiseInEither, promiseInEither,
runScript, runScript,
} from '../' } from '../'
@ -827,6 +828,20 @@ Napi4Test('accept ThreadsafeFunction Fatal', async (t) => {
}) })
}) })
Napi4Test('accept ThreadsafeFunction tuple args', async (t) => {
await new Promise<void>((resolve, reject) => {
acceptThreadsafeFunctionTupleArgs((err, num, bool, str) => {
if (err) {
return reject(err)
}
t.is(num, 1)
t.is(bool, false)
t.is(str, 'NAPI-RS')
resolve()
})
})
})
Napi4Test('object only from js', (t) => { Napi4Test('object only from js', (t) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
receiveObjectOnlyFromJs({ receiveObjectOnlyFromJs({

View file

@ -199,6 +199,7 @@ export function tsfnCallWithCallback(func: (...args: any[]) => any): void
export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void> export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void>
export function acceptThreadsafeFunction(func: (err: Error | null, value: number) => any): void export function acceptThreadsafeFunction(func: (err: Error | null, value: number) => any): void
export function acceptThreadsafeFunctionFatal(func: (value: number) => any): void export function acceptThreadsafeFunctionFatal(func: (value: number) => any): void
export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void
export function getBuffer(): Buffer export function getBuffer(): Buffer
export function appendBuffer(buf: Buffer): Buffer export function appendBuffer(buf: Buffer): Buffer
export function getEmptyBuffer(): Buffer export function getEmptyBuffer(): Buffer

View file

@ -116,3 +116,13 @@ pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction<u32, ErrorStrat
func.call(1, ThreadsafeFunctionCallMode::NonBlocking); func.call(1, ThreadsafeFunctionCallMode::NonBlocking);
}); });
} }
#[napi]
pub fn accept_threadsafe_function_tuple_args(func: ThreadsafeFunction<(u32, bool, String)>) {
thread::spawn(move || {
func.call(
Ok((1, false, "NAPI-RS".into())),
ThreadsafeFunctionCallMode::NonBlocking,
);
});
}