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)
}
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 {
match ty {
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 {
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) => {
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 {
(
format!(
@ -248,11 +274,12 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
tuple
.elems
.iter()
.map(|elem| ty_to_ts_type(elem, false, false).0)
.map(|elem| ty_to_ts_type(elem, false, false, false).0)
.collect::<Vec<_>>()
.join(", ")
),
false,
false,
)
}
}
@ -267,14 +294,18 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
.args
.iter()
.filter_map(|arg| match arg {
syn::GenericArgument::Type(generic_ty) => {
Some(ty_to_ts_type(generic_ty, false, false)).map(|(mut ty, is_struct_field)| {
if is_ts_union_type && is_ts_function_type_notation(generic_ty) {
ty = format!("({})", ty);
}
(ty, is_struct_field)
})
}
syn::GenericArgument::Type(generic_ty) => Some(ty_to_ts_type(
generic_ty,
false,
false,
should_convert_tuple_to_variadic(&rust_ty),
))
.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,
})
.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 {
ts_ty = Some(args.first().unwrap().to_owned());
} else if rust_ty == "Option" {
ts_ty = args.first().map(|(arg, _)| {
ts_ty = args.first().map(|(arg, _, _)| {
(
if is_struct_field {
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)
},
true,
false,
)
});
} else if rust_ty == "AsyncTask" {
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) {
Some((format!("Promise<{}>", o), false))
Some((format!("Promise<{}>", o), false, false))
} else {
Some(("Promise<unknown>".to_owned(), false))
Some(("Promise<unknown>".to_owned(), false, false))
}
});
} else if rust_ty == "Reference" || rust_ty == "WeakReference" {
ts_ty = r#struct::TASK_STRUCTS.with(|t| {
// Reference<T> => T
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) {
Some((o.to_owned(), false))
Some((o.to_owned(), false, false))
} else {
Some((output_type, false))
Some((output_type, false, false))
}
} else {
// 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()) {
if known_ty.contains("{}") {
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,
));
} 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
.with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned())
{
ts_ty = Some((t, false));
} else if rust_ty == "ThreadsafeFunction" {
ts_ty = Some((t, false, false));
} else if rust_ty == TSFN_RUST_TY {
let fatal_tsfn = match args.get(1) {
Some((arg, _)) => arg == "Fatal",
Some((arg, _, _)) => arg == "Fatal",
_ => 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 {
Some((format!("(value: {}) => any", first_arg), false))
Some((
{
if *is_variadic {
format!("({}) => any", args)
} else {
format!("(value: {}) => any", args)
}
},
false,
false,
))
} else {
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,
))
};
@ -354,19 +407,25 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool, is_struct_field: bool) -> (S
aliases
.borrow()
.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) => {
let (element_type, is_optional) = ty_to_ts_type(&a.elem, is_return_ty, is_struct_field);
(format!("{}[]", element_type), is_optional)
let (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!(
"export const {}: {}",
&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_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 crate::{js_doc_from_comments, CallbackArg, FnKind, NapiFn};
struct FnArg {
arg: String,
ts_type: String,
is_optional: bool,
pub(crate) struct FnArg {
pub(crate) arg: String,
pub(crate) ts_type: String,
pub(crate) is_optional: bool,
}
struct FnArgList {
pub(crate) struct FnArgList {
this: Option<FnArg>,
args: Vec<FnArg>,
last_required: Option<usize>,
@ -110,7 +110,7 @@ fn gen_callback_type(callback: &CallbackArg) -> String {
.iter()
.enumerate()
.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 {
arg: format!("arg{}", i),
ts_type,
@ -119,7 +119,7 @@ fn gen_callback_type(callback: &CallbackArg) -> String {
})
.collect::<FnArgList>(),
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(),
}
)
@ -153,7 +153,7 @@ impl NapiFn {
}) = arguments
{
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 {
arg: "this".to_owned(),
ts_type,
@ -178,7 +178,7 @@ impl NapiFn {
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 arg = path.pat.to_token_stream().to_string().to_case(Case::Camel);
@ -230,7 +230,7 @@ impl NapiFn {
.unwrap_or_else(|| "".to_owned()),
_ => {
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" {
"void".to_owned()
} else if ts_type == "Self" {

View file

@ -38,19 +38,19 @@ impl ToTypeDef for NapiImpl {
TASK_STRUCTS.with(|t| {
t.borrow_mut().insert(
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 {
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 {
"void".to_owned()
};
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 {
"void".to_owned()
};
@ -60,7 +60,7 @@ impl ToTypeDef for NapiImpl {
original_name: None,
def: format!(
"[Symbol.iterator](): Iterator<{}, {}, {}>",
ty_to_ts_type(output_type, false, true).0,
ty_to_ts_type(output_type, false, true, false).0,
return_type,
next_type,
),
@ -118,7 +118,7 @@ impl NapiStruct {
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 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> {
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 acceptThreadsafeFunction(func: (err: Error | null, 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 appendBuffer(buf: Buffer): Buffer␊
export function getEmptyBuffer(): Buffer␊

View file

@ -119,6 +119,7 @@ import {
bigintFromI64,
acceptThreadsafeFunction,
acceptThreadsafeFunctionFatal,
acceptThreadsafeFunctionTupleArgs,
promiseInEither,
runScript,
} 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) => {
return new Promise((resolve, reject) => {
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 acceptThreadsafeFunction(func: (err: Error | null, 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 appendBuffer(buf: Buffer): 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);
});
}
#[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,
);
});
}