From 16f808276d15191103b3e65559bf5a4f21716baa Mon Sep 17 00:00:00 2001 From: LongYinan Date: Sun, 23 Jan 2022 18:17:00 +0800 Subject: [PATCH] feat(napi): implement get_js_function --- crates/backend/src/codegen/fn.rs | 7 +- .../src/bindgen_runtime/module_register.rs | 74 +++++++++++++++--- examples/napi/__test__/typegen.spec.ts.md | 1 + examples/napi/__test__/typegen.spec.ts.snap | Bin 2502 -> 2512 bytes examples/napi/__test__/values.spec.ts | 11 +++ examples/napi/index.d.ts | 1 + examples/napi/src/callback.rs | 8 +- 7 files changed, 85 insertions(+), 17 deletions(-) diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 0c2300a0..c0fff9c8 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -300,10 +300,7 @@ impl NapiFn { let module_register_name = get_register_ident(&name_str); let intermediate_ident = get_intermediate_ident(&name_str); let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); - let cb_name = Ident::new( - &format!("__register__fn__{}_callback__", name_str), - Span::call_site(), - ); + let cb_name = Ident::new(&format!("{}_js_function", name_str), Span::call_site()); quote! { #[allow(non_snake_case)] #[allow(clippy::all)] @@ -322,7 +319,7 @@ impl NapiFn { "Failed to register function `{}`", #name_str, )?; - + napi::bindgen_prelude::register_js_function(#js_name, env, #cb_name, Some(#intermediate_ident)); Ok(fn_ptr) } diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs index 847e6376..195f2b7a 100644 --- a/crates/napi/src/bindgen_runtime/module_register.rs +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -6,7 +6,9 @@ use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; use lazy_static::lazy_static; -use crate::{check_status, check_status_or_throw, sys, JsError, Property, Result}; +use crate::{ + check_status, check_status_or_throw, sys, JsError, JsFunction, Property, Result, Value, ValueType, +}; pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result; pub type ModuleExportsCallback = @@ -82,12 +84,18 @@ type ModuleClassProperty = PersistedSingleThreadHashMap< HashMap, (&'static str, Vec)>, >; +type FnRegisterMap = PersistedSingleThreadHashMap< + ExportRegisterCallback, + (sys::napi_env, sys::napi_callback, &'static str), +>; + unsafe impl Send for PersistedSingleThreadHashMap {} unsafe impl Sync for PersistedSingleThreadHashMap {} lazy_static! { static ref MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default(); static ref MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default(); + static ref FN_REGISTER_MAP: FnRegisterMap = Default::default(); } #[cfg(feature = "compat-mode")] @@ -103,6 +111,7 @@ thread_local! { >> = Default::default(); } +#[doc(hidden)] pub fn get_class_constructor(js_name: &'static str) -> Option { REGISTERED_CLASSES.with(|registered_classes| { let classes = registered_classes.borrow(); @@ -110,12 +119,14 @@ pub fn get_class_constructor(js_name: &'static str) -> Option { }) } +#[doc(hidden)] #[cfg(feature = "compat-mode")] // compatibility for #[module_exports] pub fn register_module_exports(callback: ModuleExportsCallback) { MODULE_EXPORTS.push(callback); } +#[doc(hidden)] pub fn register_module_export( js_mod: Option<&'static str>, name: &'static str, @@ -124,6 +135,17 @@ pub fn register_module_export( MODULE_REGISTER_CALLBACK.push((js_mod, (name, cb))); } +#[doc(hidden)] +pub fn register_js_function( + name: &'static str, + env: sys::napi_env, + cb: ExportRegisterCallback, + c_fn: sys::napi_callback, +) { + FN_REGISTER_MAP.borrow_mut().insert(cb, (env, c_fn, name)); +} + +#[doc(hidden)] pub fn register_class( rust_name: &'static str, js_mod: Option<&'static str>, @@ -138,6 +160,40 @@ pub fn register_class( val.1.extend(props.into_iter()); } +#[inline] +pub fn get_js_function(raw_fn: ExportRegisterCallback) -> Result { + FN_REGISTER_MAP + .borrow_mut() + .get(&raw_fn) + .and_then(|(env, cb, name)| { + let mut function = ptr::null_mut(); + let name_len = name.len() - 1; + let fn_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) }; + check_status!(unsafe { + sys::napi_create_function( + *env, + fn_name.as_ptr(), + name_len, + *cb, + ptr::null_mut(), + &mut function, + ) + }) + .ok()?; + Some(JsFunction(Value { + env: *env, + value: function, + value_type: ValueType::Function, + })) + }) + .ok_or_else(|| { + crate::Error::new( + crate::Status::InvalidArg, + "JavaScript function does not exists".to_owned(), + ) + }) +} + #[no_mangle] unsafe extern "C" fn napi_register_module_v1( env: sys::napi_env, @@ -191,17 +247,13 @@ unsafe extern "C" fn napi_register_module_v1( let js_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) }; unsafe { if let Err(e) = callback(env).and_then(|v| { + let exported_object = if exports_js_mod.is_null() { + exports + } else { + exports_js_mod + }; check_status!( - sys::napi_set_named_property( - env, - if exports_js_mod.is_null() { - exports - } else { - exports_js_mod - }, - js_name.as_ptr(), - v - ), + sys::napi_set_named_property(env, exported_object, js_name.as_ptr(), v), "Failed to register export `{}`", name, ) diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 2eff2439..96a2b1a6 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -38,6 +38,7 @@ Generated by [AVA](https://avajs.dev). export function optionOnly(callback: (arg0?: string | undefined | null) => void): void␊ /** napi = { version = 2, features = ["serde-json"] } */␊ export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊ + export function returnJsFunction(): (...args: any[]) => any␊ export function eitherStringOrNumber(input: string | number): number␊ export function returnEither(input: number): string | number␊ export function either3(input: string | number | boolean): number␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index d015efb753710c966076a793e1d13029f8f72113..81724e3cbd418011b9014dca43141541124ef9d8 100644 GIT binary patch literal 2512 zcmV;>2`~0RRzVay8=_5`RKEt+S)m^9uvO z?#43KB#5X|h6&H6+&W0Ln)#O`R1FZ*_hi(1pkyUq9!9>M&|hH zZ8Ke7H%S+^9#$SelozBs;EKS13c3rWQG!eWv)(^AY+b(V{?tCb=r)N+Mm~!(h+ZNB z&7}bQ>F;G6DzDZgtvIHWS4ziR^mh!4H%u#{TWTT#Z%p+73}%!7oU>StxMHvNl3uS&CW=TWNu+uA zR(dqh64=&OQLytDoMVs1PPi`RokoIfRsFNP} zMiMgxtE=aV#UWe$sHAv)O&-c(G>eg$nBwm-_}xo@C9@iDIG9=!`fJVa?4rb%_*^CWdIIkY|XQj z-btcAn9am`O0ia7qj64>AFM*j4xt zFGB*lfLLq{ej1Vq0@cO@GJXzXhWnei zIFv~Y-&^>7!SCR68$Sux;7e`Zc_&2>FhrE`{ujdl<1%7k{R_Z+pXH7TioxjZcWs1l`G_feu< z#g-AOqc8jm&vWHV?WNT@6LiF|Nw^0$l6mAjh z$78f40G%`Fh{IP|MO)C5Wk|LyE5}=4H99^u#m<9R*mbf(p=vBRt4QD-IPwf$CCFt) zsnTDm?awT4)1NJ?9zE_$L4$yMVmWZcl?KOn%O=W$1~uza3+;4y1T5}s;YQy?(x=7< z_^^>Z(l;Arpvz5RI(2K(W7TntI;~u-D6-d?xw}bR=)a3P0sx_Xw&?at@>5!03LMdl zK&mK7sZY6zI?1>7?x=NPWN&4~VGk4KR1Q~Xh%y#J7D`fm!Cwpk_{{yKM1!6pF= z2@l^jF10#gDXBS8r`$351$xfdnXRkM=AwDZSu|%TE^q|^JA`tZlBkrgtGeKM?Ew{i zX6r9{;wf~aF%MbpyR+Nb&F$vrMMcch>nuvt#`?N9E04Z79Fr;S=R>;N-oKggwsog^Mga&t~JX}I{ zv;SGBWe!0@M>o7lXIXah&T$7T(|Ed`!OiPaF?&I&rd%jbLBA5P>B|YaIAGwE@U(d? zqYzN^L5!Ikx|$sTU+Uh*_T$0dQRvb(9s{o+7|ev_NMJ&?)0S-NQk|Y{3b&YO^>k=6 z4|WSpu}pN!hut>VEtXLPZFEIVS~he*YL6jRCyMt?`(mIk%;FLB@qvgPDGe3vvHS(+ zyc^3Oa+U2*fmV0gV+Q^gpm#_$jZS39zA(AZ77+r#_G;)9_N$ou#u)r5n4k2V_O1d0 zoEp-PzT9k>#vVtdJMvgQ>+> z0%iOm9fz1S{lV^jfVA}~;W)tn&w`eVMettJlK7Bv6F=fX9mz<1Pu_yRLSpF?J{kik zRu1BjN4S4n;h4m8c5Bxw?z7B{WLtudXq3Mp&4UusJhj^-&e7KQ^B~&sDGwhg0+{Gq*sQK)jNZ@4*iC;oNra7ekcC;j|K4 z=fW{WNn7E1#_YY!;Umqb5ioAhmWiDr&v&WjhmgI94q3zhjw;Xz`d_==vuo2&U5w-q zn|jTlb;bZScXr4j<*R6JOe@b;G3Yq^cJAeP zPTB3+@J{cp`m*aYfCb;_aPg6LlUPTrs zfJ!=qt-GC2HEheshC$8%AC|M(X!eqiRZA)SE$KC}A9F^thxa8H1C%`;I{i^fYGG^a z^2}J4aI9izemXp(dQcWt7C(F6xc)!=86_=k;q><8^gUEPR(DgmOVa*ol95>WGsZmU z#WYdYjO@mf=uS0S{5we%d-Kc4(~L47_E~~HOp9oV-#by=Db&SgfrKrgWsn-m+q?kt zQ|}UQ$}y0f8sj7M?ryMYAZJDvt#?OnIwuF6u9FH%27;SEZEsag18tN8&_?6iDUu26 z5=sEHn9zHa(<= literal 2502 zcmV;%2|4ybRzViyFuz3Q3;{7E{egAmf+?n&`=xk&yLBk4KCvLB{~8)z80#A=UKzum1PD zXMcEx|9@p1(=nb}-*HgT6GTCQe1L*h?Jr*(F; zdVXO5*xgvhngkJ5iX7Z&77H4k`X52ASKS?Qzd}gNXedQAAzy#pG#hhUiQu0SM$}~F z%g7vGy=|t;>n7>K*2BsJi1LDT2V4>OPeFH~G)j;OVAlHwhpo$Z-5=Yh7u_Zi$;fAM z2GL7Ipt%%aKmDDIL*><)q!q_>@=EELi~f#b@rG$dlpL`XT{%o#5}jn^P_VE&1wfUI z3?Q7VIoMjIfLb0aFdbUj1-t+x)@+aWkA(If2X?f*wEz-s zpnV&90gWP`2E!&nMb>jGkk>opM)D8{;GY7z9OIt@QAwdrV70NEEy+*drVwIWKnVN< z_KKoM^0?46)>T|e$XdovRKze@pv_V~X4Uut>YhSW&r9(nvKWJq!8mzM?#T_rAJTqJ z8gS-})Od?_PFMS(qp7mIr|U<8tmNzgY%ZVwgw zwwCgPFQB_f>45PY*2W5wTh0=1PKg~W18^u~Yo4X_P7?juB+&=q{wd^0!eEcw&?o`N zqcIj;p}9yT)S=A=R0@nd(u1@~)(hpx;J)P|G*(5bdo&)JibBY~?1Lw-*VB`?Jc<-# zJ19){yiGNnZ)E3Ps`MnqOoAbXL|PQTmkIF0&cFws9}>_7#A0JYfpHH!9qvG|+g$QQ zS$)>ZKPqgL-4V%&&lLR~~IPJl32QW(|AMoM1m1xo&)`*pTxOIi{gv7t%knlo*0So+B7;_eh~^i3pvYK(x76WJquvrz`R+!UtUwkADR9mlBC z%GHV@d!3oPo5Y3wyQr!I2<@{)w_lQ<()v>1h-L&*MM+A1%2m`!zO8phtqUW2D=Q9r zm?)=mxH?0Wu@JIQlIjcoVhF%z?k^=8^u)QU&51hYj>#|3bH>hW zU2QfO%~Q^zIYV)QD*)Ibl-rao01$a3GE-Og@qH$N{b zVxCWDQKB~1*S&G!$yD-)nt8bjM@B5pW9Ur53D>Zdbqewn=*$8f<9Wf(X>R}(KWmjC z*02Fc$=Ze?J#(-^HfG?IBH&Ez7P2Q5lb!N#3E9p5XQ7rk1PvYC@FtyQ+08r09jr{_ z>3Rk?uTRD71*MvDp*#isO2DSCAL!zMfm6cM=CzDMK+y*=W^(9ib^v^-dmG!22Y*MQ zOWSx1yn-Z$-wfxa+{N6^Pd9(JTORJ6wq({1ukbd;#X2UcV zSyy-o2`=4QIz*f8x-qSXmR4Hv|KEL_nMZ(hm@Q65fAD}M&f(&7W@?wOP}!37(lUd5QjX%{rL(< z9iFpWyIyghWoE3}5`09X{0(Uyl#u4B-8LCMAE*xDuxUW@+F%P076pfoMRPX1DQ119 z#?z{L!0ujbSum2H-A#c{C1+Ac>p zyIhU%IT^^&2;N?HADLr;Wh42KlmGtvU;i-sDBkTr(h;9w>*{2shj5uu2Bd{Yx>2X* z+SGFUmc`6W1kA8F7|<&tGT4Rj}K0Iv${s4q@wVCsYmF zGO}TiGr))CY&M#`u~{HtOK2IShVnKq!2Hy^#G7&qB&Wvs z2)(--Y#PX!kwxp>(VNc6L8t4af|7yY=8xN3RntHl@9AdIDm?T{l_?dPQ&qLma;^Vm@JXF}O;o8nssDXooC3C)Vo4#K< QXnk1uKjgVy`a>W90N`uHwEzGB diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 4dd4d185..e1fc69f4 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -77,6 +77,7 @@ import { JsClassForEither, receiveMutClassOrNumber, getStrFromObject, + returnJsFunction, } from '../' test('export const', (t) => { @@ -193,6 +194,16 @@ test('callback', (t) => { }) }) +test('return function', (t) => { + return new Promise((resolve) => { + returnJsFunction()((err: Error | undefined, content: string) => { + t.is(err, undefined) + t.is(content, 'hello world') + resolve() + }) + }) +}) + test('object', (t) => { t.deepEqual(listObjKeys({ name: 'John Doe', age: 20 }), ['name', 'age']) t.deepEqual(createObj(), { test: 1 }) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 4dad7295..54b7eac8 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -28,6 +28,7 @@ export function optionStartEnd(callback: (arg0: string | undefined | null, arg1: export function optionOnly(callback: (arg0?: string | undefined | null) => void): void /** napi = { version = 2, features = ["serde-json"] } */ export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void +export function returnJsFunction(): (...args: any[]) => any export function eitherStringOrNumber(input: string | number): number export function returnEither(input: number): string | number export function either3(input: string | number | boolean): number diff --git a/examples/napi/src/callback.rs b/examples/napi/src/callback.rs index 4254faca..eaa1da9b 100644 --- a/examples/napi/src/callback.rs +++ b/examples/napi/src/callback.rs @@ -1,6 +1,7 @@ -use napi::bindgen_prelude::*; use std::env; +use napi::bindgen_prelude::*; + #[napi] fn get_cwd Result<()>>(callback: T) { callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap(); @@ -40,3 +41,8 @@ fn read_file_content() -> Result { // serde_json::from_str(&s)?; Ok("hello world".to_string()) } + +#[napi] +fn return_js_function() -> Result { + get_js_function(read_file_js_function) +}