feat(napi-derive): implement #[napi(transparent)] (#2376)

This commit is contained in:
翠 / green 2024-11-30 11:28:40 +09:00 committed by GitHub
parent f116eaf5e5
commit 521aefe420
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 215 additions and 80 deletions

View File

@ -89,11 +89,19 @@ pub struct NapiStruct {
#[derive(Debug, Clone)]
pub enum NapiStructKind {
Transparent(NapiTransparent),
Class(NapiClass),
Object(NapiObject),
StructuredEnum(NapiStructuredEnum),
}
#[derive(Debug, Clone)]
pub struct NapiTransparent {
pub ty: Type,
pub object_from_js: bool,
pub object_to_js: bool,
}
#[derive(Debug, Clone)]
pub struct NapiClass {
pub fields: Vec<NapiStructField>,

View File

@ -8,7 +8,7 @@ use crate::{
codegen::{get_intermediate_ident, js_mod_to_token_stream},
BindgenResult, FnKind, NapiImpl, NapiStruct, NapiStructKind, TryToTokens,
};
use crate::{NapiClass, NapiObject, NapiStructuredEnum};
use crate::{NapiClass, NapiObject, NapiStructuredEnum, NapiTransparent};
static NAPI_IMPL_ID: AtomicU32 = AtomicU32::new(0);
const TYPED_ARRAY_TYPE: &[&str] = &[
@ -278,6 +278,7 @@ impl NapiStruct {
fn gen_napi_value_map_impl(&self) -> TokenStream {
match &self.kind {
NapiStructKind::Transparent(transparent) => self.gen_napi_value_transparent_impl(transparent),
NapiStructKind::Class(class) if !class.ctor => gen_napi_value_map_impl(
&self.name,
self.gen_to_napi_value_ctor_impl_for_non_default_constructor_struct(class),
@ -1101,6 +1102,75 @@ impl NapiStruct {
#from_napi_value
}
}
fn gen_napi_value_transparent_impl(&self, transparent: &NapiTransparent) -> TokenStream {
let name = &self.name;
let name = if self.has_lifetime {
quote! { #name<'_> }
} else {
quote! { #name }
};
let inner_type = transparent.ty.clone().into_token_stream();
let to_napi_value = if transparent.object_to_js {
quote! {
#[automatically_derived]
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<Self> {
Ok(Self(<#inner_type>::from_napi_value(env, napi_val)?))
}
}
}
} else {
quote! {}
};
let from_napi_value = if transparent.object_from_js {
quote! {
#[automatically_derived]
impl napi::bindgen_prelude::ToNapiValue for #name {
unsafe fn to_napi_value(
env: napi::bindgen_prelude::sys::napi_env,
val: Self
) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
<#inner_type>::to_napi_value(env, val.0)
}
}
}
} else {
quote! {}
};
quote! {
#[automatically_derived]
impl napi::bindgen_prelude::TypeName for #name {
fn type_name() -> &'static str {
<#inner_type>::type_name()
}
fn value_type() -> napi::ValueType {
<#inner_type>::value_type()
}
}
#[automatically_derived]
impl napi::bindgen_prelude::ValidateNapiValue for #name {
unsafe fn validate(
env: napi::bindgen_prelude::sys::napi_env,
napi_val: napi::bindgen_prelude::sys::napi_value
) -> napi::bindgen_prelude::Result<napi::sys::napi_value> {
<#inner_type>::validate(env, napi_val)
}
}
#to_napi_value
#from_napi_value
}
}
}
impl TryToTokens for NapiImpl {

View File

@ -21,6 +21,7 @@ impl ToTypeDef for NapiStruct {
Some(TypeDef {
kind: String::from(match self.kind {
NapiStructKind::Transparent(_) => "type",
NapiStructKind::Class(_) => "struct",
NapiStructKind::Object(_) => "interface",
NapiStructKind::StructuredEnum(_) => "type",
@ -137,6 +138,9 @@ impl NapiStruct {
fn gen_ts_class(&self) -> String {
match &self.kind {
NapiStructKind::Transparent(transparent) => {
ty_to_ts_type(&transparent.ty, false, false, false).0
}
NapiStructKind::Class(class) => {
let mut ctor_args = vec![];
let def = class

View File

@ -77,6 +77,7 @@ macro_rules! attrgen {
(string_enum, StringEnum(Span, Option<(String, Span)>)),
(use_nullable, UseNullable(Span, Option<bool>), false),
(discriminant, Discriminant(Span, String, Span)),
(transparent, Transparent(Span)),
// impl later
// (inspectable, Inspectable(Span)),

View File

@ -12,7 +12,7 @@ use napi_derive_backend::{
rm_raw_prefix, BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiClass,
NapiConst, NapiEnum, NapiEnumValue, NapiEnumVariant, NapiFn, NapiFnArg, NapiFnArgKind, NapiImpl,
NapiItem, NapiObject, NapiStruct, NapiStructField, NapiStructKind, NapiStructuredEnum,
NapiStructuredEnumVariant,
NapiStructuredEnumVariant, NapiTransparent,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
@ -946,7 +946,7 @@ fn convert_fields(
check_vis: bool,
) -> BindgenResult<(Vec<NapiStructField>, bool)> {
let mut napi_fields = vec![];
let mut is_tuple = false;
let is_tuple = matches!(fields, syn::Fields::Unnamed(_));
for (i, field) in fields.iter_mut().enumerate() {
if check_vis && !matches!(field.vis, syn::Visibility::Public(_)) {
continue;
@ -962,10 +962,7 @@ fn convert_fields(
),
syn::Member::Named(ident.clone()),
),
None => {
is_tuple = true;
(format!("field{}", i), syn::Member::Unnamed(i.into()))
}
None => (format!("field{}", i), syn::Member::Unnamed(i.into())),
};
let ignored = field_opts.skip().is_some();
@ -1048,7 +1045,28 @@ impl ConvertToAST for syn::ItemStruct {
generator_struct.insert(key, implement_iterator);
drop(generator_struct);
let struct_kind = if opts.object().is_some() {
let transparent = opts
.transparent()
.is_some()
.then(|| -> Result<_, Diagnostic> {
if !is_tuple || self.fields.len() != 1 {
bail_span!(
self,
"#[napi(transparent)] can only be applied to a struct with a single field tuple",
)
}
let first_field = self.fields.iter().next().unwrap();
Ok(first_field.ty.clone())
})
.transpose()?;
let struct_kind = if let Some(transparent) = transparent {
NapiStructKind::Transparent(NapiTransparent {
ty: transparent,
object_from_js: opts.object_from_js(),
object_to_js: opts.object_to_js(),
})
} else if opts.object().is_some() {
NapiStructKind::Object(NapiObject {
fields,
object_from_js: opts.object_from_js(),
@ -1066,6 +1084,7 @@ impl ConvertToAST for syn::ItemStruct {
};
match &struct_kind {
NapiStructKind::Transparent(_) => {}
NapiStructKind::Class(class) if !class.ctor => {}
_ => {
for field in self.fields.iter() {

View File

@ -536,6 +536,8 @@ Generated by [AVA](https://avajs.dev).
export declare function getModuleFileName(): string␊
export declare function getMyVec(): MyVec␊
export declare function getNestedNumArr(): number[][][]␊
export declare function getNull(): null␊
@ -602,6 +604,9 @@ Generated by [AVA](https://avajs.dev).
export declare function mutateTypedArray(input: Float32Array): void␊
export type MyVec =␊
Array<number | string>
export interface NotUseNullableStruct {␊
requiredNumberField: number␊
requiredStringField: string␊

View File

@ -205,6 +205,7 @@ import {
returnCString,
receiveBufferSliceWithLifetime,
generateFunctionAndCallIt,
getMyVec,
} from '../index.cjs'
import { test } from './test.framework.js'
@ -840,6 +841,11 @@ test('Return BufferSlice with lifetime', (t) => {
t.deepEqual(reader2.read(), Buffer.from('Hello world'))
})
test('Transparent', (t) => {
const v = getMyVec()
t.deepEqual(v, [42, 'a string'])
})
test('TypedArray', (t) => {
t.is(acceptSlice(new Uint8Array([1, 2, 3])), 3n)
t.deepEqual(u8ArrayToArray(new Uint8Array([1, 2, 3])), [1, 2, 3])

View File

@ -352,42 +352,44 @@ function __napi_rs_initialize_modules(__napiInstance) {
__napiInstance.exports['__napi_register__spawn_thread_in_thread_356']?.()
__napiInstance.exports['__napi_register__Pet_struct_357']?.()
__napiInstance.exports['__napi_register__tsfn_in_either_358']?.()
__napiInstance.exports['__napi_register__get_buffer_359']?.()
__napiInstance.exports['__napi_register__get_buffer_slice_360']?.()
__napiInstance.exports['__napi_register__append_buffer_361']?.()
__napiInstance.exports['__napi_register__get_empty_buffer_362']?.()
__napiInstance.exports['__napi_register__create_external_buffer_slice_363']?.()
__napiInstance.exports['__napi_register__create_buffer_slice_from_copied_data_364']?.()
__napiInstance.exports['__napi_register__get_empty_typed_array_365']?.()
__napiInstance.exports['__napi_register__convert_u32_array_366']?.()
__napiInstance.exports['__napi_register__create_external_typed_array_367']?.()
__napiInstance.exports['__napi_register__mutate_typed_array_368']?.()
__napiInstance.exports['__napi_register__deref_uint8_array_369']?.()
__napiInstance.exports['__napi_register__buffer_pass_through_370']?.()
__napiInstance.exports['__napi_register__buffer_with_async_block_371']?.()
__napiInstance.exports['__napi_register__array_buffer_pass_through_372']?.()
__napiInstance.exports['__napi_register__accept_slice_373']?.()
__napiInstance.exports['__napi_register__accept_arraybuffer_374']?.()
__napiInstance.exports['__napi_register__create_arraybuffer_375']?.()
__napiInstance.exports['__napi_register__u8_array_to_array_376']?.()
__napiInstance.exports['__napi_register__i8_array_to_array_377']?.()
__napiInstance.exports['__napi_register__u16_array_to_array_378']?.()
__napiInstance.exports['__napi_register__i16_array_to_array_379']?.()
__napiInstance.exports['__napi_register__u32_array_to_array_380']?.()
__napiInstance.exports['__napi_register__i32_array_to_array_381']?.()
__napiInstance.exports['__napi_register__f32_array_to_array_382']?.()
__napiInstance.exports['__napi_register__f64_array_to_array_383']?.()
__napiInstance.exports['__napi_register__u64_array_to_array_384']?.()
__napiInstance.exports['__napi_register__i64_array_to_array_385']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_386']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_387']?.()
__napiInstance.exports['__napi_register__AsyncBuffer_impl_388']?.()
__napiInstance.exports['__napi_register__async_reduce_buffer_389']?.()
__napiInstance.exports['__napi_register__async_buffer_to_array_390']?.()
__napiInstance.exports['__napi_register__u_init8_array_from_string_391']?.()
__napiInstance.exports['__napi_register__AsyncReader_impl_392']?.()
__napiInstance.exports['__napi_register__Reader_struct_393']?.()
__napiInstance.exports['__napi_register__Reader_impl_395']?.()
__napiInstance.exports['__napi_register__MyVec_struct_359']?.()
__napiInstance.exports['__napi_register__get_my_vec_360']?.()
__napiInstance.exports['__napi_register__get_buffer_361']?.()
__napiInstance.exports['__napi_register__get_buffer_slice_362']?.()
__napiInstance.exports['__napi_register__append_buffer_363']?.()
__napiInstance.exports['__napi_register__get_empty_buffer_364']?.()
__napiInstance.exports['__napi_register__create_external_buffer_slice_365']?.()
__napiInstance.exports['__napi_register__create_buffer_slice_from_copied_data_366']?.()
__napiInstance.exports['__napi_register__get_empty_typed_array_367']?.()
__napiInstance.exports['__napi_register__convert_u32_array_368']?.()
__napiInstance.exports['__napi_register__create_external_typed_array_369']?.()
__napiInstance.exports['__napi_register__mutate_typed_array_370']?.()
__napiInstance.exports['__napi_register__deref_uint8_array_371']?.()
__napiInstance.exports['__napi_register__buffer_pass_through_372']?.()
__napiInstance.exports['__napi_register__buffer_with_async_block_373']?.()
__napiInstance.exports['__napi_register__array_buffer_pass_through_374']?.()
__napiInstance.exports['__napi_register__accept_slice_375']?.()
__napiInstance.exports['__napi_register__accept_arraybuffer_376']?.()
__napiInstance.exports['__napi_register__create_arraybuffer_377']?.()
__napiInstance.exports['__napi_register__u8_array_to_array_378']?.()
__napiInstance.exports['__napi_register__i8_array_to_array_379']?.()
__napiInstance.exports['__napi_register__u16_array_to_array_380']?.()
__napiInstance.exports['__napi_register__i16_array_to_array_381']?.()
__napiInstance.exports['__napi_register__u32_array_to_array_382']?.()
__napiInstance.exports['__napi_register__i32_array_to_array_383']?.()
__napiInstance.exports['__napi_register__f32_array_to_array_384']?.()
__napiInstance.exports['__napi_register__f64_array_to_array_385']?.()
__napiInstance.exports['__napi_register__u64_array_to_array_386']?.()
__napiInstance.exports['__napi_register__i64_array_to_array_387']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_388']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_389']?.()
__napiInstance.exports['__napi_register__AsyncBuffer_impl_390']?.()
__napiInstance.exports['__napi_register__async_reduce_buffer_391']?.()
__napiInstance.exports['__napi_register__async_buffer_to_array_392']?.()
__napiInstance.exports['__napi_register__u_init8_array_from_string_393']?.()
__napiInstance.exports['__napi_register__AsyncReader_impl_394']?.()
__napiInstance.exports['__napi_register__Reader_struct_395']?.()
__napiInstance.exports['__napi_register__Reader_impl_397']?.()
}
export const Animal = __napiModule.exports.Animal
export const AnimalWithDefaultConstructor = __napiModule.exports.AnimalWithDefaultConstructor
@ -539,6 +541,7 @@ export const getGlobal = __napiModule.exports.getGlobal
export const getIndexMapping = __napiModule.exports.getIndexMapping
export const getMapping = __napiModule.exports.getMapping
export const getModuleFileName = __napiModule.exports.getModuleFileName
export const getMyVec = __napiModule.exports.getMyVec
export const getNestedNumArr = __napiModule.exports.getNestedNumArr
export const getNull = __napiModule.exports.getNull
export const getNumArr = __napiModule.exports.getNumArr

View File

@ -376,42 +376,44 @@ function __napi_rs_initialize_modules(__napiInstance) {
__napiInstance.exports['__napi_register__spawn_thread_in_thread_356']?.()
__napiInstance.exports['__napi_register__Pet_struct_357']?.()
__napiInstance.exports['__napi_register__tsfn_in_either_358']?.()
__napiInstance.exports['__napi_register__get_buffer_359']?.()
__napiInstance.exports['__napi_register__get_buffer_slice_360']?.()
__napiInstance.exports['__napi_register__append_buffer_361']?.()
__napiInstance.exports['__napi_register__get_empty_buffer_362']?.()
__napiInstance.exports['__napi_register__create_external_buffer_slice_363']?.()
__napiInstance.exports['__napi_register__create_buffer_slice_from_copied_data_364']?.()
__napiInstance.exports['__napi_register__get_empty_typed_array_365']?.()
__napiInstance.exports['__napi_register__convert_u32_array_366']?.()
__napiInstance.exports['__napi_register__create_external_typed_array_367']?.()
__napiInstance.exports['__napi_register__mutate_typed_array_368']?.()
__napiInstance.exports['__napi_register__deref_uint8_array_369']?.()
__napiInstance.exports['__napi_register__buffer_pass_through_370']?.()
__napiInstance.exports['__napi_register__buffer_with_async_block_371']?.()
__napiInstance.exports['__napi_register__array_buffer_pass_through_372']?.()
__napiInstance.exports['__napi_register__accept_slice_373']?.()
__napiInstance.exports['__napi_register__accept_arraybuffer_374']?.()
__napiInstance.exports['__napi_register__create_arraybuffer_375']?.()
__napiInstance.exports['__napi_register__u8_array_to_array_376']?.()
__napiInstance.exports['__napi_register__i8_array_to_array_377']?.()
__napiInstance.exports['__napi_register__u16_array_to_array_378']?.()
__napiInstance.exports['__napi_register__i16_array_to_array_379']?.()
__napiInstance.exports['__napi_register__u32_array_to_array_380']?.()
__napiInstance.exports['__napi_register__i32_array_to_array_381']?.()
__napiInstance.exports['__napi_register__f32_array_to_array_382']?.()
__napiInstance.exports['__napi_register__f64_array_to_array_383']?.()
__napiInstance.exports['__napi_register__u64_array_to_array_384']?.()
__napiInstance.exports['__napi_register__i64_array_to_array_385']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_386']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_387']?.()
__napiInstance.exports['__napi_register__AsyncBuffer_impl_388']?.()
__napiInstance.exports['__napi_register__async_reduce_buffer_389']?.()
__napiInstance.exports['__napi_register__async_buffer_to_array_390']?.()
__napiInstance.exports['__napi_register__u_init8_array_from_string_391']?.()
__napiInstance.exports['__napi_register__AsyncReader_impl_392']?.()
__napiInstance.exports['__napi_register__Reader_struct_393']?.()
__napiInstance.exports['__napi_register__Reader_impl_395']?.()
__napiInstance.exports['__napi_register__MyVec_struct_359']?.()
__napiInstance.exports['__napi_register__get_my_vec_360']?.()
__napiInstance.exports['__napi_register__get_buffer_361']?.()
__napiInstance.exports['__napi_register__get_buffer_slice_362']?.()
__napiInstance.exports['__napi_register__append_buffer_363']?.()
__napiInstance.exports['__napi_register__get_empty_buffer_364']?.()
__napiInstance.exports['__napi_register__create_external_buffer_slice_365']?.()
__napiInstance.exports['__napi_register__create_buffer_slice_from_copied_data_366']?.()
__napiInstance.exports['__napi_register__get_empty_typed_array_367']?.()
__napiInstance.exports['__napi_register__convert_u32_array_368']?.()
__napiInstance.exports['__napi_register__create_external_typed_array_369']?.()
__napiInstance.exports['__napi_register__mutate_typed_array_370']?.()
__napiInstance.exports['__napi_register__deref_uint8_array_371']?.()
__napiInstance.exports['__napi_register__buffer_pass_through_372']?.()
__napiInstance.exports['__napi_register__buffer_with_async_block_373']?.()
__napiInstance.exports['__napi_register__array_buffer_pass_through_374']?.()
__napiInstance.exports['__napi_register__accept_slice_375']?.()
__napiInstance.exports['__napi_register__accept_arraybuffer_376']?.()
__napiInstance.exports['__napi_register__create_arraybuffer_377']?.()
__napiInstance.exports['__napi_register__u8_array_to_array_378']?.()
__napiInstance.exports['__napi_register__i8_array_to_array_379']?.()
__napiInstance.exports['__napi_register__u16_array_to_array_380']?.()
__napiInstance.exports['__napi_register__i16_array_to_array_381']?.()
__napiInstance.exports['__napi_register__u32_array_to_array_382']?.()
__napiInstance.exports['__napi_register__i32_array_to_array_383']?.()
__napiInstance.exports['__napi_register__f32_array_to_array_384']?.()
__napiInstance.exports['__napi_register__f64_array_to_array_385']?.()
__napiInstance.exports['__napi_register__u64_array_to_array_386']?.()
__napiInstance.exports['__napi_register__i64_array_to_array_387']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_388']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_389']?.()
__napiInstance.exports['__napi_register__AsyncBuffer_impl_390']?.()
__napiInstance.exports['__napi_register__async_reduce_buffer_391']?.()
__napiInstance.exports['__napi_register__async_buffer_to_array_392']?.()
__napiInstance.exports['__napi_register__u_init8_array_from_string_393']?.()
__napiInstance.exports['__napi_register__AsyncReader_impl_394']?.()
__napiInstance.exports['__napi_register__Reader_struct_395']?.()
__napiInstance.exports['__napi_register__Reader_impl_397']?.()
}
module.exports.Animal = __napiModule.exports.Animal
module.exports.AnimalWithDefaultConstructor = __napiModule.exports.AnimalWithDefaultConstructor
@ -563,6 +565,7 @@ module.exports.getGlobal = __napiModule.exports.getGlobal
module.exports.getIndexMapping = __napiModule.exports.getIndexMapping
module.exports.getMapping = __napiModule.exports.getMapping
module.exports.getModuleFileName = __napiModule.exports.getModuleFileName
module.exports.getMyVec = __napiModule.exports.getMyVec
module.exports.getNestedNumArr = __napiModule.exports.getNestedNumArr
module.exports.getNull = __napiModule.exports.getNull
module.exports.getNumArr = __napiModule.exports.getNumArr

View File

@ -514,6 +514,7 @@ module.exports.getGlobal = nativeBinding.getGlobal
module.exports.getIndexMapping = nativeBinding.getIndexMapping
module.exports.getMapping = nativeBinding.getMapping
module.exports.getModuleFileName = nativeBinding.getModuleFileName
module.exports.getMyVec = nativeBinding.getMyVec
module.exports.getNestedNumArr = nativeBinding.getNestedNumArr
module.exports.getNull = nativeBinding.getNull
module.exports.getNumArr = nativeBinding.getNumArr

View File

@ -526,6 +526,8 @@ export declare function getMapping(): Record<string, number>
export declare function getModuleFileName(): string
export declare function getMyVec(): MyVec
export declare function getNestedNumArr(): number[][][]
export declare function getNull(): null
@ -592,6 +594,9 @@ export declare function mutateOptionalExternal(external: ExternalObject<number>
export declare function mutateTypedArray(input: Float32Array): void
export type MyVec =
Array<number | string>
export interface NotUseNullableStruct {
requiredNumberField: number
requiredStringField: string

View File

@ -68,4 +68,5 @@ mod string;
mod symbol;
mod task;
mod threadsafe_function;
mod transparent;
mod typed_array;

View File

@ -0,0 +1,9 @@
use napi::Either;
#[napi(transparent)]
struct MyVec(Vec<Either<u32, String>>);
#[napi]
fn get_my_vec() -> MyVec {
MyVec(vec![Either::A(42), Either::B("a string".to_owned())])
}