From 3386bb98676c660cdf366f52a97e1afe87088144 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Mon, 15 Nov 2021 18:56:06 +0800 Subject: [PATCH] feat(napi): support JsGlobal in `Env` --- .github/workflows/lint.yaml | 2 +- README.md | 2 +- crates/backend/src/typegen.rs | 1 + crates/napi/src/bindgen_runtime/env.rs | 59 ++++++++++++++---- .../src/bindgen_runtime/js_values/object.rs | 49 +++++---------- .../src/bindgen_runtime/js_values/serde.rs | 11 ++-- crates/napi/src/env.rs | 27 +++----- crates/napi/src/js_values/global.rs | 8 +-- crates/napi/src/js_values/object.rs | 12 ---- examples/napi-compat-mode/src/array.rs | 2 +- examples/napi/__test__/typegen.spec.ts.md | 3 + examples/napi/__test__/typegen.spec.ts.snap | Bin 1013 -> 1050 bytes examples/napi/__test__/values.spec.ts | 19 ++++++ examples/napi/index.d.ts | 36 ++++++++--- examples/napi/src/object.rs | 17 ++++- 15 files changed, 148 insertions(+), 100 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index cb368c0b..7fd43bb2 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -61,7 +61,7 @@ jobs: run: cargo fmt -- --check - name: Clippy - run: cargo clippy + run: cargo clippy --all-features - name: Clear the cargo caches run: | diff --git a/README.md b/README.md index c6d4a5b6..0398a0b4 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ yarn test | T: Fn(...) -> Result | Function | 1 | v8.0.0 | | Async/Future | Promise | 4 | v10.6.0 | async | | AsyncTask | Promise | 1 | v8.5.0 | -| (NOT YET) | global | 1 | v8.0.0 | +| JsGlobal | global | 1 | v8.0.0 | | JsSymbol | Symbol | 1 | v8.0.0 | | (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 | | JsFunction | threadsafe function | 4 | v10.6.0 | napi4 | diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 9cb61de4..814cfcd8 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -75,6 +75,7 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("external", "object"), ("AbortSignal", "AbortSignal"), ("JsFunction", "(...args: any[]) => any"), + ("JsGlobal", "typeof global"), ]); map diff --git a/crates/napi/src/bindgen_runtime/env.rs b/crates/napi/src/bindgen_runtime/env.rs index b7628670..f1e05145 100644 --- a/crates/napi/src/bindgen_runtime/env.rs +++ b/crates/napi/src/bindgen_runtime/env.rs @@ -1,22 +1,57 @@ -use crate::{sys, Result}; +use std::cell::RefCell; +use std::ptr; -use super::{Array, Object}; +use crate::{check_status, sys, JsGlobal, JsNull, JsUndefined, NapiValue, Result}; -#[repr(transparent)] -pub struct Env(sys::napi_env); +use super::Array; -impl From for Env { - fn from(raw_env: sys::napi_env) -> Env { - Env(raw_env) - } +pub use crate::Env; + +thread_local! { + static JS_UNDEFINED: RefCell> = RefCell::default(); + static JS_NULL: RefCell> = RefCell::default(); } impl Env { - pub fn create_object(&self) -> Result { - Object::new(self.0) - } - pub fn create_array(&self, len: u32) -> Result { Array::new(self.0, len) } + + /// Get [JsUndefined](./struct.JsUndefined.html) value + pub fn get_undefined(&self) -> Result { + if let Some(js_undefined) = JS_UNDEFINED.with(|x| *x.borrow()) { + return Ok(js_undefined); + } + let mut raw_value = ptr::null_mut(); + check_status!(unsafe { sys::napi_get_undefined(self.0, &mut raw_value) })?; + let js_undefined = unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) }; + JS_UNDEFINED.with(|x| x.borrow_mut().replace(js_undefined)); + Ok(js_undefined) + } + + pub fn get_null(&self) -> Result { + if let Some(js_null) = JS_NULL.with(|cell| *cell.borrow()) { + return Ok(js_null); + } + let mut raw_value = ptr::null_mut(); + check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?; + let js_null = unsafe { JsNull::from_raw_unchecked(self.0, raw_value) }; + JS_NULL.with(|js_null_cell| { + js_null_cell.borrow_mut().replace(js_null); + }); + Ok(js_null) + } + + pub fn get_global(&self) -> Result { + let mut global = std::ptr::null_mut(); + crate::check_status!( + unsafe { sys::napi_get_global(self.0, &mut global) }, + "Get global object from Env failed" + )?; + Ok(JsGlobal(crate::Value { + value: global, + env: self.0, + value_type: crate::ValueType::Object, + })) + } } diff --git a/crates/napi/src/bindgen_runtime/js_values/object.rs b/crates/napi/src/bindgen_runtime/js_values/object.rs index 4729b274..3b33fcb4 100644 --- a/crates/napi/src/bindgen_runtime/js_values/object.rs +++ b/crates/napi/src/bindgen_runtime/js_values/object.rs @@ -1,10 +1,7 @@ -use crate::{bindgen_prelude::*, check_status, sys, type_of, ValueType}; +use crate::{bindgen_prelude::*, check_status, sys, type_of, JsObject, ValueType}; use std::{ffi::CString, ptr}; -pub struct Object { - pub(crate) env: sys::napi_env, - pub(crate) inner: sys::napi_value, -} +pub type Object = JsObject; impl Object { pub(crate) fn new(env: sys::napi_env) -> Result { @@ -16,7 +13,11 @@ impl Object { )?; } - Ok(Object { env, inner: ptr }) + Ok(Self(crate::Value { + env, + value: ptr, + value_type: ValueType::Object, + })) } pub fn get, V: FromNapiValue>(&self, field: K) -> Result> { @@ -26,17 +27,17 @@ impl Object { let mut ret = ptr::null_mut(); check_status!( - sys::napi_get_named_property(self.env, self.inner, c_field.as_ptr(), &mut ret), + sys::napi_get_named_property(self.0.env, self.0.value, c_field.as_ptr(), &mut ret), "Failed to get property with field `{}`", c_field.to_string_lossy(), )?; - let ty = type_of!(self.env, ret)?; + let ty = type_of!(self.0.env, ret)?; Ok(if ty == ValueType::Undefined { None } else { - Some(V::from_napi_value(self.env, ret)?) + Some(V::from_napi_value(self.0.env, ret)?) }) } } @@ -45,10 +46,10 @@ impl Object { let c_field = CString::new(field.as_ref())?; unsafe { - let napi_val = V::to_napi_value(self.env, val)?; + let napi_val = V::to_napi_value(self.0.env, val)?; check_status!( - sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val), + sys::napi_set_named_property(self.0.env, self.0.value, c_field.as_ptr(), napi_val), "Failed to set property with field `{}`", c_field.to_string_lossy(), )?; @@ -61,12 +62,12 @@ impl Object { let mut names = ptr::null_mut(); unsafe { check_status!( - sys::napi_get_property_names(obj.env, obj.inner, &mut names), + sys::napi_get_property_names(obj.0.env, obj.0.value, &mut names), "Failed to get property names of given object" )?; } - let names = unsafe { Array::from_napi_value(obj.env, names)? }; + let names = unsafe { Array::from_napi_value(obj.0.env, names)? }; let mut ret = vec![]; for i in 0..names.len() { @@ -86,25 +87,3 @@ impl TypeName for Object { ValueType::Object } } - -impl ToNapiValue for Object { - unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result { - Ok(val.inner) - } -} - -impl FromNapiValue for Object { - unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { - let value_type = type_of!(env, napi_val)?; - match value_type { - ValueType::Object => Ok(Self { - inner: napi_val, - env, - }), - _ => Err(Error::new( - Status::InvalidArg, - "Given napi value is not an object".to_owned(), - )), - } - } -} diff --git a/crates/napi/src/bindgen_runtime/js_values/serde.rs b/crates/napi/src/bindgen_runtime/js_values/serde.rs index ff1add21..b7ab5cca 100644 --- a/crates/napi/src/bindgen_runtime/js_values/serde.rs +++ b/crates/napi/src/bindgen_runtime/js_values/serde.rs @@ -1,6 +1,8 @@ use serde_json::{Map, Value}; -use crate::{bindgen_runtime::Null, check_status, sys, type_of, Error, Result, Status, ValueType}; +use crate::{ + bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType, +}; use super::{FromNapiValue, Object, ToNapiValue}; @@ -78,10 +80,11 @@ impl ToNapiValue for Map { impl FromNapiValue for Map { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { - let obj = Object { + let obj = JsObject(crate::Value { env, - inner: napi_val, - }; + value: napi_val, + value_type: ValueType::Object, + }); let mut map = Map::new(); for key in Object::keys(&obj)?.into_iter() { diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index e57943e1..e8f2b686 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -43,25 +43,18 @@ pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys /// Notification of this event is delivered through the callbacks given to `Env::add_env_cleanup_hook` and `Env::set_instance_data`. pub struct Env(pub(crate) sys::napi_env); +impl From for Env { + fn from(env: sys::napi_env) -> Self { + Env(env) + } +} + impl Env { #[allow(clippy::missing_safety_doc)] pub unsafe fn from_raw(env: sys::napi_env) -> Self { Env(env) } - /// Get [JsUndefined](./struct.JsUndefined.html) value - pub fn get_undefined(&self) -> Result { - let mut raw_value = ptr::null_mut(); - check_status!(unsafe { sys::napi_get_undefined(self.0, &mut raw_value) })?; - Ok(unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) }) - } - - pub fn get_null(&self) -> Result { - let mut raw_value = ptr::null_mut(); - check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?; - Ok(unsafe { JsNull::from_raw_unchecked(self.0, raw_value) }) - } - pub fn get_boolean(&self, value: bool) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) })?; @@ -231,7 +224,7 @@ impl Env { Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) }) } - pub fn create_array(&self) -> Result { + pub fn create_empty_array(&self) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_create_array(self.0, &mut raw_value) })?; Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) }) @@ -949,12 +942,6 @@ impl Env { result } - pub fn get_global(&self) -> Result { - let mut raw_global = ptr::null_mut(); - check_status!(unsafe { sys::napi_get_global(self.0, &mut raw_global) })?; - Ok(unsafe { JsGlobal::from_raw_unchecked(self.0, raw_global) }) - } - pub fn get_napi_version(&self) -> Result { let global = self.get_global()?; let process: JsObject = global.get_named_property("process")?; diff --git a/crates/napi/src/js_values/global.rs b/crates/napi/src/js_values/global.rs index aff3cbad..b6ce46de 100644 --- a/crates/napi/src/js_values/global.rs +++ b/crates/napi/src/js_values/global.rs @@ -9,7 +9,7 @@ pub struct JsTimeout(pub(crate) Value); impl JsGlobal { pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result { - let func: JsFunction = self.get_named_property("setInterval")?; + let func: JsFunction = self.get_named_property_unchecked("setInterval")?; func .call( None, @@ -24,14 +24,14 @@ impl JsGlobal { } pub fn clear_interval(&self, timer: JsTimeout) -> Result { - let func: JsFunction = self.get_named_property("clearInterval")?; + let func: JsFunction = self.get_named_property_unchecked("clearInterval")?; func .call(None, &[timer.into_unknown()]) .and_then(|ret| ret.try_into()) } pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result { - let func: JsFunction = self.get_named_property("setTimeout")?; + let func: JsFunction = self.get_named_property_unchecked("setTimeout")?; func .call( None, @@ -46,7 +46,7 @@ impl JsGlobal { } pub fn clear_timeout(&self, timer: JsTimeout) -> Result { - let func: JsFunction = self.get_named_property("clearTimeout")?; + let func: JsFunction = self.get_named_property_unchecked("clearTimeout")?; func .call(None, &[timer.into_unknown()]) .and_then(|ret| ret.try_into()) diff --git a/crates/napi/src/js_values/object.rs b/crates/napi/src/js_values/object.rs index e1727041..59fbab1a 100644 --- a/crates/napi/src/js_values/object.rs +++ b/crates/napi/src/js_values/object.rs @@ -8,7 +8,6 @@ use std::ptr; #[cfg(feature = "napi5")] use super::check_status; use super::Value; -use crate::bindgen_runtime::TypeName; #[cfg(feature = "napi5")] use crate::sys; #[cfg(feature = "napi5")] @@ -17,20 +16,9 @@ use crate::Env; use crate::Error; #[cfg(feature = "napi5")] use crate::Result; -use crate::ValueType; pub struct JsObject(pub(crate) Value); -impl TypeName for JsObject { - fn type_name() -> &'static str { - "Object" - } - - fn value_type() -> crate::ValueType { - ValueType::Object - } -} - #[cfg(feature = "napi5")] pub struct FinalizeContext { pub env: Env, diff --git a/examples/napi-compat-mode/src/array.rs b/examples/napi-compat-mode/src/array.rs index 3df64fd6..b880054f 100644 --- a/examples/napi-compat-mode/src/array.rs +++ b/examples/napi-compat-mode/src/array.rs @@ -7,7 +7,7 @@ use napi::{ #[contextless_function] fn test_create_array(env: Env) -> ContextlessResult { - env.create_array().map(Some) + env.create_empty_array().map(Some) } #[js_function(1)] diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index f8b7e4d6..df52895a 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -34,6 +34,9 @@ Generated by [AVA](https://avajs.dev). export function fibonacci(n: number): number␊ export function listObjKeys(obj: object): Array␊ export function createObj(): object␊ + export function getGlobal(): typeof global␊ + export function getUndefined(): JsUndefined␊ + export function getNull(): JsNull␊ export function asyncPlus100(p: Promise): Promise␊ interface PackageJson {␊ name: string␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 31e68ac416b8426170c562dc603077c7db5bb4a1..a856162423c3619fa4ff97832b3909479d66fe7e 100644 GIT binary patch literal 1050 zcmV+#1m*idRzVPmB2C*Y z607u0!412cNuQ{!GE>v-x^2WVCd@ENd=kPb%dUh8AgaV(B{cM6ys@F>p}!mK>iFnC zkQm>yS>nfp^N7Tg5UeBpWfb7m3EWGTAc6hY8NepNg;G+%k5Gt&rcBTz06Z8}%y=RS z&bJz%%#5kJbvoB-*zf^J_opOntAFlbhOnFO32&rmf{lLFpqVo(?aa9+d{ln&r& z4S!Z>BW>Ya*wrIk3tHmwNn1DyV@2)WVE~u1P$(S*aLXQ0I1b=~Wn*jM^8iN7>3)GK zR9KQewD^c-?&Q_sfltGA+!NbaH|w~p!V=`F zaNB!IStJFCW9EzPV9vC`e7&J_?aSzW2>79~+1mNa8b(d)b*a-RzV`M7}ANLuQi>$G*%0lo^C=8 zio6DtMILkA1ezlEKs+~VSkx`0hLYwqxe^)go0()}LhUGm(i7CV)hZW;zdS33!%Y^c zQ~I*Vthk>lLUmf1sp+Di7c{g zs{tYw|TWERdsVe1#&KgUL2S+VNQHYhwIF(EDc(Ys& zDdTVxzk{cJfQP1;=N3c9TMeOvyp3o%F5!)k2|U4tO5qiBLpY}vg2gMl^G;u8$50k5-Mh-<}<5HTC60+Nva{lbeZ?$OFxyWyXAot*x7VTy|jz@?E&C zo`xb;k}@WU+z!r!v3Or^_{kiA{-Qye9#u>BZ!&oMHsvJ6LGOON0+|}pRz|Jx{=7(pmslk`Y z$G7GrpOG=2MRQ<`>rS+))k2BLl5g$Ul^B1tm92W>))I6vr7tEz(?3M?Tv$0&B(wjm$<33N-OAh1h=i7YhIYd#LnE diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index c14d46b0..fd818df4 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -41,6 +41,9 @@ import { callThreadsafeFunction, threadsafeFunctionThrowError, asyncPlus100, + getGlobal, + getUndefined, + getNull, } from '../' test('number', (t) => { @@ -137,6 +140,22 @@ test('object', (t) => { t.deepEqual(createObj(), { test: 1 }) }) +test('global', (t) => { + t.is(getGlobal(), global) +}) + +test('get undefined', (t) => { + for (const _ of Array.from({ length: 100 })) { + t.is(getUndefined(), undefined) + } +}) + +test('get null', (t) => { + for (const _ of Array.from({ length: 100 })) { + t.is(getNull(), null) + } +}) + test('Option', (t) => { t.is(mapOption(null), null) t.is(mapOption(3), 4) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index c1fda3e2..d850dddf 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -7,7 +7,9 @@ export function bigintAdd(a: BigInt, b: BigInt): BigInt export function createBigInt(): BigInt export function createBigIntI64(): BigInt 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 function eitherStringOrNumber(input: string | number): number export function returnEither(input: number): string | number export function either3(input: string | number | boolean): number @@ -15,8 +17,21 @@ interface Obj { v: string | number } export function either4(input: string | number | boolean | Obj): number -export enum Kind { Dog = 0, Cat = 1, Duck = 2 } -export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 } +export enum Kind { + Dog = 0, + Cat = 1, + Duck = 2, +} +export enum CustomNumEnum { + One = 1, + Two = 2, + Three = 3, + Four = 4, + Six = 6, + Eight = 8, + Nine = 9, + Ten = 10, +} export function enumToI32(e: CustomNumEnum): number export function throwError(): void export function mapOption(val: number | null): number | null @@ -24,6 +39,9 @@ export function add(a: number, b: number): number export function fibonacci(n: number): number export function listObjKeys(obj: object): Array export function createObj(): object +export function getGlobal(): typeof global +export function getUndefined(): JsUndefined +export function getNull(): JsNull export function asyncPlus100(p: Promise): Promise interface PackageJson { name: string @@ -38,7 +56,11 @@ export function concatStr(s: string): string export function concatUtf16(s: string): string export function concatLatin1(s: string): string export function withoutAbortController(a: number, b: number): Promise -export function withAbortController(a: number, b: number, signal: AbortSignal): Promise +export function withAbortController( + a: number, + b: number, + signal: AbortSignal, +): Promise export function callThreadsafeFunction(callback: (...args: any[]) => any): void export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void export function getBuffer(): Buffer @@ -52,14 +74,10 @@ export class Animal { static getDogKind(): Kind } export class Blake2BHasher { - static withKey(key: Blake2bKey): Blake2BHasher } -export class Blake2BKey { - -} +export class Blake2BKey {} export class Context { - constructor() static withData(data: string): Context method(): string diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs index 690addea..71c868a1 100644 --- a/examples/napi/src/object.rs +++ b/examples/napi/src/object.rs @@ -1,4 +1,4 @@ -use napi::bindgen_prelude::*; +use napi::{bindgen_prelude::*, JsGlobal, JsNull, JsUndefined}; #[napi] fn list_obj_keys(obj: Object) -> Vec { @@ -12,3 +12,18 @@ fn create_obj(env: Env) -> Object { obj } + +#[napi] +fn get_global(env: Env) -> Result { + env.get_global() +} + +#[napi] +fn get_undefined(env: Env) -> Result { + env.get_undefined() +} + +#[napi] +fn get_null(env: Env) -> Result { + env.get_null() +}