From e78cdd3c229972091e9ffba8839400f05cc3e101 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 5 Nov 2021 18:31:36 +0800 Subject: [PATCH] feat(napi): support static class factory --- crates/backend/src/ast.rs | 1 + crates/backend/src/codegen/fn.rs | 38 +++++++++++---- crates/backend/src/typegen/fn.rs | 1 + crates/macro/src/parser/attrs.rs | 1 + crates/macro/src/parser/mod.rs | 4 ++ .../napi/src/bindgen_runtime/callback_info.rs | 46 +++++++++++++++++- .../src/bindgen_runtime/module_register.rs | 11 ++++- crates/napi/src/error.rs | 15 +++++- examples/napi/__test__/typegen.spec.ts.md | 5 ++ examples/napi/__test__/typegen.spec.ts.snap | Bin 807 -> 841 bytes examples/napi/__test__/values.spec.ts | 15 ++++++ examples/napi/index.d.ts | 5 ++ examples/napi/src/class.rs | 8 +++ examples/napi/src/class_factory.rs | 14 ++++++ examples/napi/src/lib.rs | 1 + 15 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 examples/napi/src/class_factory.rs diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index bc0f35b2..cb7421dd 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -34,6 +34,7 @@ pub enum NapiFnArgKind { pub enum FnKind { Normal, Constructor, + Factory, Getter, Setter, } diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 8eb50426..930dab6d 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -39,17 +39,33 @@ impl TryToTokens for NapiFn { } }; - let function_call = - if args_len == 0 && self.fn_self.is_none() && self.kind != FnKind::Constructor { - quote! { #native_call } - } else { - quote! { - CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| { - #(#arg_conversions)* - #native_call - }) + let function_call = if args_len == 0 + && self.fn_self.is_none() + && self.kind != FnKind::Constructor + && self.kind != FnKind::Factory + { + quote! { #native_call } + } else if self.kind == FnKind::Constructor { + quote! { + let call_from_factory = ___CALL_FROM_FACTORY.load(std::sync::atomic::Ordering::Relaxed); + // constructor function is called from class `factory` + // so we should skip the original `constructor` logic + if call_from_factory { + return std::ptr::null_mut(); } - }; + CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| { + #(#arg_conversions)* + #native_call + }) + } + } else { + quote! { + CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| { + #(#arg_conversions)* + #native_call + }) + } + }; (quote! { #(#attrs)* @@ -229,6 +245,8 @@ impl NapiFn { if let Some(ty) = &self.ret { if self.kind == FnKind::Constructor { quote! { cb.construct(#js_name, #ret) } + } else if self.kind == FnKind::Factory { + quote! { cb.factory(#js_name, #ret) } } else if self.is_ret_result { if self.is_async { quote! { diff --git a/crates/backend/src/typegen/fn.rs b/crates/backend/src/typegen/fn.rs index 32f15f39..9d7c719a 100644 --- a/crates/backend/src/typegen/fn.rs +++ b/crates/backend/src/typegen/fn.rs @@ -74,6 +74,7 @@ impl NapiFn { Some(_) => "", None => "static", }, + crate::FnKind::Factory => "static", crate::FnKind::Constructor => "", crate::FnKind::Getter => "get", crate::FnKind::Setter => "set", diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index d3a98d1b..a8660405 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -44,6 +44,7 @@ macro_rules! attrgen { $mac! { (js_name, JsName(Span, String, Span)), (constructor, Constructor(Span)), + (factory, Factory(Span)), (getter, Getter(Span, Option)), (setter, Setter(Span, Option)), (readonly, Readonly(Span)), diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index cf002a7f..6708c032 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -641,6 +641,10 @@ fn fn_kind(opts: &BindgenAttrs) -> FnKind { kind = FnKind::Constructor; } + if opts.factory().is_some() { + kind = FnKind::Factory; + } + kind } diff --git a/crates/napi/src/bindgen_runtime/callback_info.rs b/crates/napi/src/bindgen_runtime/callback_info.rs index a7726bea..0e68c7c0 100644 --- a/crates/napi/src/bindgen_runtime/callback_info.rs +++ b/crates/napi/src/bindgen_runtime/callback_info.rs @@ -1,10 +1,19 @@ +use std::ffi::c_void; +use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; + use crate::{bindgen_prelude::*, check_status, sys, Result}; -use std::{ffi::c_void, ptr}; + +#[doc(hidden)] +/// Determined is `constructor` called from Class `factory` +/// Ugly but works +/// We can even be more ugly without `atomic` +pub static ___CALL_FROM_FACTORY: AtomicBool = AtomicBool::new(false); pub struct CallbackInfo { env: sys::napi_env, this: sys::napi_value, - args: [sys::napi_value; N], + pub args: [sys::napi_value; N], } impl CallbackInfo { @@ -76,6 +85,39 @@ impl CallbackInfo { Ok(this) } + pub fn factory(&self, js_name: &str, obj: T) -> Result { + let obj = Box::new(obj); + let this = self.this(); + let mut instance = ptr::null_mut(); + unsafe { + ___CALL_FROM_FACTORY.store(true, Ordering::Relaxed); + let status = sys::napi_new_instance(self.env, this, 0, ptr::null_mut(), &mut instance); + ___CALL_FROM_FACTORY.store(false, Ordering::Relaxed); + // Error thrown in `constructor` + if status == sys::Status::napi_pending_exception { + let mut exception = ptr::null_mut(); + sys::napi_get_and_clear_last_exception(self.env, &mut exception); + sys::napi_throw(self.env, exception); + return Ok(ptr::null_mut()); + } + + check_status!( + sys::napi_wrap( + self.env, + instance, + Box::into_raw(obj) as *mut std::ffi::c_void, + Some(raw_finalize_unchecked::), + ptr::null_mut(), + &mut std::ptr::null_mut() + ), + "Failed to initialize class `{}`", + js_name, + )?; + }; + + Ok(instance) + } + pub fn unwrap_borrow_mut(&mut self) -> Result<&'static mut T> where T: FromNapiMutRef + TypeName, diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs index 3632a0d6..77597a7a 100644 --- a/crates/napi/src/bindgen_runtime/module_register.rs +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -79,10 +79,10 @@ unsafe extern "C" fn napi_register_module_v1( let (ctor, props): (Vec<_>, Vec<_>) = props.into_iter().partition(|prop| prop.is_ctor); // one or more or zero? // zero is for `#[napi(task)]` - if ctor.is_empty() { + if ctor.is_empty() && props.is_empty() { continue; } - let ctor = ctor[0].raw().method.unwrap(); + let ctor = ctor.get(0).map(|c| c.raw().method.unwrap()).unwrap_or(noop); let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect(); let js_class_name = CString::new(js_name).unwrap(); @@ -143,3 +143,10 @@ unsafe extern "C" fn napi_register_module_v1( exports } + +pub(crate) unsafe extern "C" fn noop( + _env: sys::napi_env, + _info: sys::napi_callback_info, +) -> sys::napi_value { + ptr::null_mut() +} diff --git a/crates/napi/src/error.rs b/crates/napi/src/error.rs index 3d6201ce..ec45ba2d 100644 --- a/crates/napi/src/error.rs +++ b/crates/napi/src/error.rs @@ -161,9 +161,22 @@ macro_rules! impl_object_methods { /// /// This function is safety if env is not null ptr. pub unsafe fn throw_into(self, env: sys::napi_env) { + #[cfg(debug_assertions)] + let reason = self.0.reason.clone(); + #[cfg(debug_assertions)] + let status = self.0.status; let js_error = self.into_value(env); + #[cfg(debug_assertions)] let throw_status = sys::napi_throw(env, js_error); - debug_assert!(throw_status == sys::Status::napi_ok); + sys::napi_throw(env, js_error); + #[cfg(debug_assertions)] + assert!( + throw_status == sys::Status::napi_ok, + "Throw error failed, status: [{}], raw message: \"{}\", raw status: [{}]", + Status::from(throw_status), + reason, + Status::from(status) + ); } pub fn throw(&self, env: sys::napi_env) -> Result<()> { diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index f2b5d698..a1601ee9 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -48,9 +48,14 @@ Generated by [AVA](https://avajs.dev). export class Animal {␊ readonly kind: Kind␊ constructor(kind: Kind, name: string)␊ + static withKind(kind: Kind): Animal␊ get name(): string␊ set name(name: string)␊ whoami(): string␊ static getDogKind(): Kind␊ }␊ + export class ClassWithFactory {␊ + name: string␊ + static withName(name: string): ClassWithFactory␊ + }␊ ` diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 6203a4e44a19da29d5863431126a64675308161e..f7edba929500b6946a9cdc28cb2a19a416538781 100644 GIT binary patch literal 841 zcmV-P1GfA@RzVzbvn>uykm?Q^hcK6MDv*VdDc%HZ6ZGL|C>*t@3-@ZJ5@%Q(S?_W1O`eGnZ1u+OLJsP>M_rwNYI3kGGc~+gVxwiY$$o zDg$+vIv}3w1uXhbt&)>sBtGQ{Yv_rf{*ahK2-8T==UmB@Yj*fL>-Cl?Vrb2A>{F5? z5uw*1_(b)0W(06_3?s>7WU_yi+GT>4Qc}Su$VAL~T(CF*JgDc)cp|bSSt-D{88Fp# zJ{{F|8SuFnW@e7bZvQVsCR3v2sB-6-&jL$D1bY>tXhc#bjEDz$E(}vWLK$?TE4bYT z7?ox2Dunh|3ZaF(rQ0%G!WkDa+`@_M!x8KR&?E+pdH^RGy+&&n?&k2#OdDxyu4PxZ z&=G8kzZgpkM`NIv-P;S`RAvgT{Q$cB291LNTD(873NHg_bLaaCU6`;WJFw)AD03m- z?Ctt2oENWtduE{I*p-uCRV67Ic81pSME6hIWNtfwB2q#%@X zU#tcvTpN5}XKbQ<8C`{d4@S+(p+T{?3`0+Q>#0wnvd*#BNT0pch_n?ZNXqh>u?5GJ zMv}r#%!UkGQBclsw7h2&N7ubJcBYsd=jpM~(aE~#@+-C@p%9&ozOY<2fC|TLyatZ2 z*?P`*)~w}RNnr>V+Sgd3be4z!49*;83iI&3hn6u=VsOs*X=b_whiXN{e=xoJfv?wv zzaxf=`a1YA&H|a4MufXfyquCrg0rilO!FaM^;xlO>re%td0*HLa_FvGtig{do_=5K z%br>}h9*R7Xb7H?#1*P74=IufT;r$;-N3?K%nxQFqXwJ&;UJhR>l)&;!4y(=AqyyA zZ9h2`L}5J6)2|g8KAOHCN`~8Uvfm8;%R#3yGfF3jNq>8n_6GB}nGCtBqanU>3UV?1dv;Y(~ zjhHF}wM{c1?&}3C#!jtLkYXf0;|Z(lv7r8dn0^S;lVHq+k}22h=!NZemoehe8l%{! zBuOGduS4*O>g~=F!0`zTC66(a`>EtECuk}q6?_0IV%Ft?#R1?(EzgWQ!Y0W|0>(|B zsZJKtR;{c8J{JRP<}x|=|0-mx63r}C9z2U#$Wn2F{R&bvA}JF_#63M1hN&*04BF8Z zOtt}rWt!UxqJx!0Xkp&cZ5b}%oQoJHa4LIn47&j|h(V(kz^SFzXzju69KK<-k*?>O zZZ(0nU{n0%NIEzgeZ`z_KY%l76MI>~joZ!X?>-C3i%c zOZjqt&u8I0dG+g=zLKM?o&2gUNy(r+aF#zLNqAqeti=}ArC<`rKFL7=MB&PQ>heem zLMivfYH-4}!S_06W9`f6Dg^vu)T|T@inC=HM!H*1V+x&hfwM+>?3G5O>o7r5miLTn zIHoivDR^QwU^t3`a)#3KhEbHRJ8qmzF+R!5B=gbVF!Y*9KZL;waghna#N-p$am43rqu89%kAQwXSLMEqORtsVM$ zUHEHaxTvjzAE6dVYw8ht8+bV-lLWP^qD^y=ulieY+15!FfaX2nCMZL9vc($tvBcB= z7x&mxE631;XbpA2Q<7wj>e@q!WDM6RRbeJr=*9YAB`h^K { t.is(dog.name, '可乐') }) +test('class factory', (t) => { + const duck = ClassWithFactory.withName('Default') + t.is(duck.name, 'Default') + + duck.name = '周黑鸭' + t.is(duck.name, '周黑鸭') + + const doge = Animal.withKind(Kind.Dog) + t.is(doge.name, 'Default') + + doge.name = '旺财' + t.is(doge.name, '旺财') +}) + test('callback', (t) => { getCwd((cwd) => { t.is(cwd, process.cwd()) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index eb922314..2f236609 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -38,8 +38,13 @@ export function getBuffer(): Buffer export class Animal { readonly kind: Kind constructor(kind: Kind, name: string) + static withKind(kind: Kind): Animal get name(): string set name(name: string) whoami(): string static getDogKind(): Kind } +export class ClassWithFactory { + name: string + static withName(name: string): ClassWithFactory +} diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index 444de5ba..9b05a683 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -19,6 +19,14 @@ impl Animal { Animal { kind, name } } + #[napi(factory)] + pub fn with_kind(kind: Kind) -> Self { + Animal { + kind, + name: "Default".to_owned(), + } + } + #[napi(getter)] pub fn get_name(&self) -> &str { self.name.as_str() diff --git a/examples/napi/src/class_factory.rs b/examples/napi/src/class_factory.rs new file mode 100644 index 00000000..d5c7076f --- /dev/null +++ b/examples/napi/src/class_factory.rs @@ -0,0 +1,14 @@ +use napi::bindgen_prelude::*; + +#[napi] +pub struct ClassWithFactory { + pub name: String, +} + +#[napi] +impl ClassWithFactory { + #[napi(factory)] + pub fn with_name(name: String) -> Self { + Self { name } + } +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index b99f81f2..5da94157 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -7,6 +7,7 @@ mod array; mod r#async; mod callback; mod class; +mod class_factory; mod either; mod r#enum; mod error;