diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index 54659e59..63bcb09e 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -263,6 +263,21 @@ impl NapiStruct { } } + pub fn into_instance(self, env: napi::Env) -> napi::Result> { + if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) { + unsafe { + let wrapped_value = Box::leak(Box::new(self)); + let instance_value = #name::new_instance(env.raw(), wrapped_value as *mut _ as *mut std::ffi::c_void, ctor_ref)?; + + Ok(napi::bindgen_prelude::ClassInstance::<#name>::new(instance_value, wrapped_value)) + } + } else { + Err(napi::bindgen_prelude::Error::new( + napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_raw)) + ) + } + } + unsafe fn new_instance( env: napi::sys::napi_env, wrapped_value: *mut std::ffi::c_void, diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 5e76f71a..3f835ef1 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -175,6 +175,7 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("Buffer", "Buffer"), ("Vec", "Array<{}>"), ("Result", "Error | {}"), + ("ClassInstance", "{}"), ("Either", "{} | {}"), ("Either3", "{} | {} | {}"), ("Either4", "{} | {} | {} | {}"), diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index d8669bf8..cb1af7f8 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -8,6 +8,7 @@ mod arraybuffer; mod bigint; mod boolean; mod buffer; +mod class; #[cfg(all(feature = "chrono_date", feature = "napi5"))] mod date; mod either; @@ -34,6 +35,7 @@ pub use arraybuffer::*; #[cfg(feature = "napi6")] pub use bigint::*; pub use buffer::*; +pub use class::*; pub use either::*; pub use external::*; #[cfg(feature = "napi4")] diff --git a/crates/napi/src/bindgen_runtime/js_values/class.rs b/crates/napi/src/bindgen_runtime/js_values/class.rs new file mode 100644 index 00000000..c2dab55c --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/class.rs @@ -0,0 +1,65 @@ +use std::any::type_name; +use std::ops::{Deref, DerefMut}; +use std::ptr; + +use crate::{bindgen_runtime::FromNapiValue, check_status, sys, NapiRaw}; + +pub struct ClassInstance { + pub value: sys::napi_value, + inner: &'static mut T, +} + +impl ClassInstance { + #[doc(hidden)] + pub fn new(value: sys::napi_value, inner: &'static mut T) -> Self { + Self { value, inner } + } +} + +impl NapiRaw for ClassInstance { + unsafe fn raw(&self) -> sys::napi_value { + self.value + } +} + +impl FromNapiValue for ClassInstance { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result { + let mut value = ptr::null_mut(); + check_status!( + unsafe { sys::napi_unwrap(env, napi_val, &mut value) }, + "Unwrap value [{}] from class failed", + type_name::(), + )?; + let value = unsafe { Box::from_raw(value as *mut T) }; + Ok(Self { + value: napi_val, + inner: Box::leak(value), + }) + } +} + +impl Deref for ClassInstance { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner + } +} + +impl DerefMut for ClassInstance { + fn deref_mut(&mut self) -> &mut T { + self.inner + } +} + +impl AsRef for ClassInstance { + fn as_ref(&self) -> &T { + self.inner + } +} + +impl AsMut for ClassInstance { + fn as_mut(&mut self) -> &mut T { + self.inner + } +} diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index f4698cc0..cfc7453c 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -41,6 +41,11 @@ Generated by [AVA](https://avajs.dev). export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊ export function returnJsFunction(): (...args: any[]) => any␊ export function callbackReturnPromise(functionInput: () => T | Promise, callback: (err: Error | null, result: T) => void): T | Promise␊ + export interface ObjectFieldClassInstance {␊ + bird: Bird␊ + }␊ + export function createObjectWithClassField(): ObjectFieldClassInstance␊ + export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird␊ export function dateToNumber(input: Date): number␊ export function chronoDateToMillis(input: Date): number␊ export function chronoDateAdd1Minute(input: Date): Date␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 40c7c246..a7eaeb36 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 2a6012e1..412e053e 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -97,6 +97,8 @@ import { eitherFromOption, overrideIndividualArgOnFunction, overrideIndividualArgOnFunctionWithCbArg, + createObjectWithClassField, + receiveObjectWithClassField, } from '../' test('export const', (t) => { @@ -206,6 +208,12 @@ test('class Factory return Result', (t) => { t.is(c.method(), 'not empty') }) +test('class in object field', (t) => { + const obj = createObjectWithClassField() + t.is(obj.bird.name, 'Carolyn') + t.is(receiveObjectWithClassField(obj), obj.bird) +}) + test('should be able to create object reference and shared reference', (t) => { const repo = new JsRepo('.') t.is(repo.remote().name(), 'origin') diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index b59573d7..0531adfe 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -31,6 +31,11 @@ export function optionOnly(callback: (arg0?: string | undefined | null) => void) export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void export function returnJsFunction(): (...args: any[]) => any export function callbackReturnPromise(functionInput: () => T | Promise, callback: (err: Error | null, result: T) => void): T | Promise +export interface ObjectFieldClassInstance { + bird: Bird +} +export function createObjectWithClassField(): ObjectFieldClassInstance +export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird export function dateToNumber(input: Date): number export function chronoDateToMillis(input: Date): number export function chronoDateAdd1Minute(input: Date): Date diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index 2f4bec89..9d3c937e 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -1,4 +1,7 @@ -use napi::{bindgen_prelude::Buffer, Result}; +use napi::{ + bindgen_prelude::{Buffer, ClassInstance}, + Env, Result, +}; use crate::r#enum::Kind; @@ -305,3 +308,25 @@ impl Optional { } } } + +#[napi(object)] +pub struct ObjectFieldClassInstance { + pub bird: ClassInstance, +} + +#[napi] +pub fn create_object_with_class_field(env: Env) -> Result { + Ok(ObjectFieldClassInstance { + bird: Bird { + name: "Carolyn".to_owned(), + } + .into_instance(env)?, + }) +} + +#[napi] +pub fn receive_object_with_class_field( + object: ObjectFieldClassInstance, +) -> Result> { + Ok(object.bird) +}