From c8352a1fb01fba8bc7f47fb09113cf73f3d7bf51 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 24 Jan 2023 14:51:16 +0800 Subject: [PATCH] feat(napi-derive): allow partial implement From/To Napivalue for Object (#1448) --- crates/backend/src/ast.rs | 2 + crates/backend/src/codegen/struct.rs | 68 ++++++++++++-------- crates/macro/src/parser/attrs.rs | 2 + crates/macro/src/parser/mod.rs | 2 + examples/napi/__test__/typegen.spec.ts.md | 5 ++ examples/napi/__test__/typegen.spec.ts.snap | Bin 3690 -> 3730 bytes examples/napi/__test__/values.spec.ts | 17 +++++ examples/napi/index.d.ts | 5 ++ examples/napi/src/object.rs | 25 ++++++- 9 files changed, 99 insertions(+), 27 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 2070fc7f..b907775e 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -80,6 +80,8 @@ pub struct NapiStruct { pub fields: Vec, pub is_tuple: bool, pub kind: NapiStructKind, + pub object_from_js: bool, + pub object_to_js: bool, pub js_mod: Option, pub comments: Vec, pub implement_iterator: bool, diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index a4895680..fa9a150a 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -558,6 +558,46 @@ impl NapiStruct { } }; + let to_napi_value = if self.object_to_js { + quote! { + impl napi::bindgen_prelude::ToNapiValue for #name { + unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name) -> napi::bindgen_prelude::Result { + let env_wrapper = napi::bindgen_prelude::Env::from(env); + let mut obj = env_wrapper.create_object()?; + + let #destructed_fields = val; + #(#obj_field_setters)* + + napi::bindgen_prelude::Object::to_napi_value(env, obj) + } + } + } + } else { + quote! {} + }; + + let from_napi_value = if self.object_from_js { + quote! { + impl napi::bindgen_prelude::FromNapiValue for #name { + unsafe fn from_napi_value( + env: napi::bindgen_prelude::sys::napi_env, + napi_val: napi::bindgen_prelude::sys::napi_value + ) -> napi::bindgen_prelude::Result { + let env_wrapper = napi::bindgen_prelude::Env::from(env); + let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?; + + #(#obj_field_getters)* + + let val = #destructed_fields; + + Ok(val) + } + } + } + } else { + quote! {} + }; + quote! { impl napi::bindgen_prelude::TypeName for #name { fn type_name() -> &'static str { @@ -569,33 +609,9 @@ impl NapiStruct { } } - impl napi::bindgen_prelude::ToNapiValue for #name { - unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name) -> napi::bindgen_prelude::Result { - let env_wrapper = napi::bindgen_prelude::Env::from(env); - let mut obj = env_wrapper.create_object()?; + #to_napi_value - let #destructed_fields = val; - #(#obj_field_setters)* - - napi::bindgen_prelude::Object::to_napi_value(env, obj) - } - } - - impl napi::bindgen_prelude::FromNapiValue for #name { - unsafe fn from_napi_value( - env: napi::bindgen_prelude::sys::napi_env, - napi_val: napi::bindgen_prelude::sys::napi_value - ) -> napi::bindgen_prelude::Result { - let env_wrapper = napi::bindgen_prelude::Env::from(env); - let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?; - - #(#obj_field_getters)* - - let val = #destructed_fields; - - Ok(val) - } - } + #from_napi_value impl napi::bindgen_prelude::ValidateNapiValue for #name {} } diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index c9c6fd04..a0fcdd94 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -56,6 +56,8 @@ macro_rules! attrgen { (strict, Strict(Span)), (return_if_invalid, ReturnIfInvalid(Span)), (object, Object(Span)), + (object_from_js, ObjectFromJs(Span, Option)), + (object_to_js, ObjectToJs(Span, Option)), (custom_finalize, CustomFinalize(Span)), (namespace, Namespace(Span, String, Span)), (iterator, Iterator(Span)), diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index cb659923..f4b1a2b6 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -995,6 +995,8 @@ impl ConvertToAST for syn::ItemStruct { fields, is_tuple, kind: struct_kind, + object_from_js: opts.object_from_js(), + object_to_js: opts.object_to_js(), js_mod: namespace, comments: extract_doc_comments(&self.attrs), implement_iterator, diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 7e9a0429..e16b013e 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -172,6 +172,11 @@ Generated by [AVA](https://avajs.dev). }␊ export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number }␊ export function getterFromObj(): number␊ + export interface ObjectOnlyFromJs {␊ + count: number␊ + callback: (err: Error | null, value: number) => any␊ + }␊ + export function receiveObjectOnlyFromJs(obj: { count: number, callback: (err: Error | null, count: number) => void }): void␊ export function asyncPlus100(p: Promise): Promise␊ /** This is an interface for package.json */␊ export interface PackageJson {␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index fc83e0b5ab0f3794d78fe5fca70ebc8edfbb84cb..1cd4acce2df8225927a4ad2cf59eda1482f2b922 100644 GIT binary patch literal 3730 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 literal 3690 zcmV-w4wdmiRzVIWQvMPk_ZR_kGLzCnoDy((P#c55?x zGL2cjt{;mC00000000B6TkCEdw-ruX6b1aR4^V)0QDoLwJC^Ji?!p(WR#GC{k_>5G zCvc3IJHy?jH9H&+Ig&PxfaVSQyMK!03Ht~=haApuF5F!u_DwUK!;goDhv!E6=jkLC z;hFsD7pAz1+2!BmT;y>?W-MVDRg8op%UGz0TBeLdY)Ym)W?UvT1xh7<`Zl?vJ1S`S*YM@{3zU$(Sc<5OGN-F(bFWxB-n!H{Ny{v6bdZ+_qyzWU?ej z!y%>K#IzAl_ zNRlrmEVC5TJP8#S3CK^q7Fi^N-hd3Vj4mHZmGNZuL=W*5Q<6xrU`2Q74B|o@-;k#_m_Ph)#@l1jU5nX6Wdd3I1phS7|wv&oUShO}R{%+CZs_cUb4dpTk zgOsW{EQ1ye^n96#1()p6PClJ>&6HxN!#q}ed?5mw&Fo-KU!RmFd>yFWW@HBs?ysOU&7cCW9^4?K}=K7qHbr}mZn2-^@$ z2v1;({|GA=#~Y4us%WNsqjbQUfv-6cJ;;jLtQ|+cZGVF49)s0ta&awKv@u|zIeAPz zl5?;xi2ays_sJBrG|!lXw>LK>%OZCBy%fpKx8$nJ2HD-(mdRe0iOl8Y6D)SdfVbqC z+%FnXb%Q&1?f@_tHkvHoyww=+)ZTvugC`op8iVnZz)~C}X|4cIBaDHiil$GT>9H)U zW@i=|HnImx$drkP}Dx$j&%wdKkvpd&cqMaE0}9y zrg1Kh6Ba1&z60`_M{4eOUJ!Dyu{hEn18x>}7ux(i!g(eVv8($X@;K)5y8Xa&Y#s6> zSFF(!f3z0~TcutRiFTzRBZMW<)8eJQJ`7~ZGUF1Cv-(pn|8s+E#6;DZY6nKYUzG9#X~ zc!#DW#}V~h zp9|8U;3;5}=L>R9;~Y!?%`k~d6qpVHdBweLX)FSn0=MLDE4|QtpYtTr(?--Cnw{wk zfskD>1J8Z8UwnBEmP~4sUwj`?1>d*q_c4`fS@g`qGYkPL;W@+YEmEMv@roSiTam+e zOav$3GR_D{2)Je`%s7HY&EO|V*hM+9T$<_L>7LFgmr7ugvWIUt4w!n&D1`DlHo?bv z#<2Y!zU+%UgXjDBe9AB3@c};W@!1?F`4C@@xW@U1?#wX8*}B`M9x&`!9NgOu*r3kL ziUbKP|AqF*fiGT3X~IMASL1ALx*ACS19SFbBaQWCEp?D0SwN|S0dW$i2Aa(^I|BZa zA67j0Sn*3{kt=s%>wx;GDgVUp<>Wi%1q#H`vFijDBBE^LT-Nq9X=4CMoq1Z{W4ugR zWCkpD{3|d#4|U51&__{hG@8Ax*6)Pgp3j=#fpUHs;_6!gZ9Z&NkKy^_6vB*r)Vm8+5dh zSMkhTgH8tIL%G@@!?Y}%>357jG2arTNfeg7S<=#=> z5|D0ZJYok)#Lsz@(|DN8j+1h4KumXRNFtV0m4m6>F{RGCbla^6{<`DpENuc!ZYHn9 zg~I|)c5V9J$)3ZVr5&2pX9neqv2;V|SuvYr@nVFjlC zIa^B1-t8K;3fJCZ!s;6GSFxB-Ed)G55K}VK4PB(`MWJibZIL=@M;u!NX55#KHfyR4 z?{vnVuE-{eOs$25;(>ncF*^0boq!Vy4__P%Pc>IV8_Mrs+}f4;=pDl%u={y26VL+< zfEm$>8BCKCGl!^vKo?L$>l0Q$dFxE*ZbH=%V<|__Z4L_w3^^3QkJPD^6}ARImON}CajMf<6mH_ zI{hGRR6|lsEb#9~eaP`L&gItKyPL%ewNtCakq~BTDcE|@_d8hTb+WUH=%pcehTmJO zT;!3E{@F4z>|o3W3s{p}7T=s+5lb0ti6rDqK5f}y$eW*NqVwJLbX|%@BZq60rSsY) zc<1vG`t-DVul4o&SH+|{#d`UPfV~bhX@Ccxx*E_MbeE(dO`|-KfrLypWR<6d^dER~ zqBvi80|+6-?XXGAXNX^^>DGhI$zM>&-?uiQ&+NQ6WJkh2)-mTng8FXaPbUubcnq`m zinz6B>04wMuro!jh7)KVft)iD$FToAZgO3KjcA)d^@-#&o$6}pQ~k05KE_;F+n|x8 zQzuWVLNQNq$zvl`3u|0eD|GhtTk-Zmx8p+*u}=*530WM)LgpD8nS&O6&U1ZKIn`$~ zIy|;#G`|{2pJ^k^S_rE!2-R`S9fmAb9ST#xb{tU^eyPy-HN5)p^(xsxu7wEZtSG+p zY-ryeHAPczYc%=P$pJh)uv~?e$+gg6%fJPxSNFDCwVa}9?IntOq0Ola^=detVpzM5 zv>yxV4BjcVh-GYA6`auIRBzhs=+{w97Z%?{u7u;i<^p6^G$EhVRCP(C=R|7KHa15^YK)wbx|or)O%A@{53f`?Oq$mP-qg7r zV~su(iv>%RX-76OP-P48o|FIm_doyA?KqK(xu6SPW6+(wnJ1Xl+O&bwT`|)W@43=9 zqkRp#OrOH&3s>QsW*|VkbA@Lao)aG+gGh7|Uoy$%;I2Zrps zH*Y^)eevDk=FM^nCapiNj^Xf(Sj;-UXs@GBBSwxU*b{Q%tLYr-g%1*uEznX`aiu6L zGT8dDjK{Bf%^Lwmv+*>kBjR_}vaS@3{)l-!0IgLch$NMh0O>{y%gvoI$2{25LpiR# zg}4*bGqyd!nlZx@BJ;ju-gPo`x)5hr#KNFm+YjBZxL?R?c~CJE7s8&}mph6rgEIya zEe3}7-k>omA$c0<8of>mN<*jZekc2YQt=KioeUw-N74{G2C3}Qq~r^_oUkLtqN)aq zr2##>YquM#(yPyK7nm+MFT?s`lschn%xg}eqBds9*}+#`<`h+b*PNC*y5FTt#?+Lm z;?u2ujj*MvM?85?$9bk=)-^~mMP6Q0IiA`8C{NB3ap4xBjY%5mfPpOWlrlkdqBQ(p z!Jz&7mzsS_*u@D==O`1052N>oRGy7QZ1#Q0Pq86tZN_?_B`hw<;J-cdqoHEY!G)Q19l-_lbQvRG1o95cE@=4%!KnS(>El_l~2Gsc@&{R+^^C0 zekJ#<)jVOTXe~y>Gk3#e5D_b8E?b@G=Pn#J!@Z-iqwyU_viH7!aNlNV_?nb}RTj;xmZ*1DAy>kL@;H{bub^_tlO61J)b^ Iv3E590CDadq5uE@ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 87ad5b3b..818a06c2 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -89,6 +89,7 @@ import { returnJsFunction, testSerdeRoundtrip, createObjWithProperty, + receiveObjectOnlyFromJs, dateToNumber, chronoDateToMillis, derefUint8Array, @@ -816,6 +817,22 @@ Napi4Test('accept ThreadsafeFunction Fatal', async (t) => { }) }) +Napi4Test('object only from js', (t) => { + return new Promise((resolve, reject) => { + receiveObjectOnlyFromJs({ + count: 100, + callback: (err: Error | null, count: number) => { + if (err) { + reject(err) + } else { + t.is(count, 100) + resolve() + } + }, + }) + }) +}) + const Napi5Test = Number(process.versions.napi) >= 5 ? test : test.skip Napi5Test('Date test', (t) => { diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index df0dcced..5b2f39c9 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -162,6 +162,11 @@ export interface TsTypeChanged { } export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number } export function getterFromObj(): number +export interface ObjectOnlyFromJs { + count: number + callback: (err: Error | null, value: number) => any +} +export function receiveObjectOnlyFromJs(obj: { count: number, callback: (err: Error | null, count: number) => void }): void export function asyncPlus100(p: Promise): Promise /** This is an interface for package.json */ export interface PackageJson { diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs index 56c81b9e..63ee457f 100644 --- a/examples/napi/src/object.rs +++ b/examples/napi/src/object.rs @@ -1,4 +1,7 @@ -use napi::{bindgen_prelude::*, JsGlobal, JsNull, JsObject, JsUndefined, Property}; +use napi::{ + bindgen_prelude::*, threadsafe_function::ThreadsafeFunction, JsGlobal, JsNull, JsObject, + JsUndefined, Property, +}; #[napi] fn list_obj_keys(obj: Object) -> Vec { @@ -101,3 +104,23 @@ pub fn create_obj_with_property(env: Env) -> Result { fn getter_from_obj() -> u32 { 42 } + +#[napi(object, object_to_js = false)] +struct ObjectOnlyFromJs { + pub count: u32, + pub callback: ThreadsafeFunction, +} + +#[napi] +fn receive_object_only_from_js( + #[napi(ts_arg_type = "{ count: number, callback: (err: Error | null, count: number) => void }")] + obj: ObjectOnlyFromJs, +) { + let ObjectOnlyFromJs { callback, count } = obj; + std::thread::spawn(move || { + callback.call( + Ok(count), + napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, + ); + }); +}