feat(napi-derive): add use_nullable attribute (#1971)

* feat(napi-derive): add use_nullable attribute

Co-authored-by: naskya <m@naskya.net>

* chore(napi-derive): update tests

Co-authored-by: naskya <m@naskya.net>

---------

Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
サポミク 2024-02-24 22:49:54 +09:00 committed by GitHub
parent 43415251b8
commit ebe97257a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 190 additions and 25 deletions

View file

@ -88,6 +88,7 @@ pub struct NapiStruct {
pub implement_iterator: bool,
pub use_custom_finalize: bool,
pub register_name: Ident,
pub use_nullable: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -493,15 +493,24 @@ impl NapiStruct {
let alias_ident = format_ident!("{}_", ident);
field_destructions.push(quote! { #ident: #alias_ident });
if is_optional_field {
obj_field_setters.push(quote! {
obj_field_setters.push(match self.use_nullable {
false => quote! {
if #alias_ident.is_some() {
obj.set(#field_js_name, #alias_ident)?;
}
},
true => quote! {
if let Some(#alias_ident) = #alias_ident {
obj.set(#field_js_name, #alias_ident)?;
} else {
obj.set(#field_js_name, napi::bindgen_prelude::Null)?;
}
},
});
} else {
obj_field_setters.push(quote! { obj.set(#field_js_name, #alias_ident)?; });
}
if is_optional_field {
if is_optional_field && !self.use_nullable {
obj_field_getters.push(quote! { let #alias_ident: #ty = obj.get(#field_js_name)?; });
} else {
obj_field_getters.push(quote! {
@ -515,15 +524,24 @@ impl NapiStruct {
syn::Member::Unnamed(i) => {
field_destructions.push(quote! { arg #i });
if is_optional_field {
obj_field_setters.push(quote! {
obj_field_setters.push(match self.use_nullable {
false => quote! {
if arg #1.is_some() {
obj.set(#field_js_name, arg #i)?;
}
},
true => quote! {
if let Some(arg #i) = arg #i {
obj.set(#field_js_name, arg #i)?;
} else {
obj.set(#field_js_name, napi::bindgen_prelude::Null)?;
}
},
});
} else {
obj_field_setters.push(quote! { obj.set(#field_js_name, arg #1)?; });
}
if is_optional_field {
if is_optional_field && !self.use_nullable {
obj_field_getters.push(quote! { let arg #i: #ty = obj.get(#field_js_name)?; });
} else {
obj_field_getters.push(quote! {

View file

@ -128,8 +128,13 @@ impl NapiStruct {
let (arg, is_optional) = ty_to_ts_type(&f.ty, false, true, false);
let arg = f.ts_type.as_ref().map(|ty| ty.to_string()).unwrap_or(arg);
let sep = if is_optional { "?" } else { "" };
let arg = format!("{}{}: {}", &f.js_name, sep, arg);
let arg = match is_optional {
false => format!("{}: {}", &f.js_name, arg),
true => match self.use_nullable {
false => format!("{}?: {}", &f.js_name, arg),
true => format!("{}: {} | null", &f.js_name, arg),
},
};
if self.kind == NapiStructKind::Constructor {
ctor_args.push(arg.clone());
}

View file

@ -55,15 +55,15 @@ macro_rules! attrgen {
(getter, Getter(Span, Option<Ident>)),
(setter, Setter(Span, Option<Ident>)),
(readonly, Readonly(Span)),
(enumerable, Enumerable(Span, Option<bool>)),
(writable, Writable(Span, Option<bool>)),
(configurable, Configurable(Span, Option<bool>)),
(enumerable, Enumerable(Span, Option<bool>), true),
(writable, Writable(Span, Option<bool>), true),
(configurable, Configurable(Span, Option<bool>), true),
(skip, Skip(Span)),
(strict, Strict(Span)),
(return_if_invalid, ReturnIfInvalid(Span)),
(object, Object(Span)),
(object_from_js, ObjectFromJs(Span, Option<bool>)),
(object_to_js, ObjectToJs(Span, Option<bool>)),
(object_from_js, ObjectFromJs(Span, Option<bool>), true),
(object_to_js, ObjectToJs(Span, Option<bool>), true),
(custom_finalize, CustomFinalize(Span)),
(namespace, Namespace(Span, String, Span)),
(iterator, Iterator(Span)),
@ -72,6 +72,7 @@ macro_rules! attrgen {
(ts_type, TsType(Span, String, Span)),
(ts_generic_types, TsGenericTypes(Span, String, Span)),
(string_enum, StringEnum(Span)),
(use_nullable, UseNullable(Span, Option<bool>), false),
// impl later
// (inspectable, Inspectable(Span)),
@ -86,8 +87,8 @@ macro_rules! attrgen {
}
macro_rules! methods {
($(($name:ident, $variant:ident($($contents:tt)*)),)*) => {
$(methods!(@method $name, $variant($($contents)*));)*
($(($name:ident, $variant:ident($($contents:tt)*) $($extra_tokens:tt)*),)*) => {
$(methods!(@method $name, $variant($($contents)*) $($extra_tokens)*);)*
#[cfg(feature = "strict")]
#[allow(unused)]
@ -131,7 +132,7 @@ macro_rules! methods {
}
};
(@method $name:ident, $variant:ident(Span, Option<bool>)) => {
(@method $name:ident, $variant:ident(Span, Option<bool>), $default_value:literal) => {
pub fn $name(&self) -> bool {
self.attrs
.iter()
@ -143,7 +144,7 @@ macro_rules! methods {
_ => None,
})
.next()
.unwrap_or(true)
.unwrap_or($default_value)
}
};
@ -265,11 +266,11 @@ impl Default for BindgenAttrs {
}
macro_rules! gen_bindgen_attr {
($( ($method:ident, $($variants:tt)*) ,)*) => {
($( ($method:ident, $variant:ident($($associated_data:tt)*) $($extra_tokens:tt)*) ,)*) => {
/// The possible attributes in the `#[napi]`.
#[derive(Debug)]
pub enum BindgenAttr {
$($($variants)*,)*
$($variant($($associated_data)*)),*
}
}
}
@ -395,7 +396,7 @@ impl Parse for BindgenAttr {
return Ok(BindgenAttr::$variant(attr_span, val, span))
});
(@parser $variant:ident(Span, Option<bool>)) => ({
(@parser $variant:ident(Span, Option<bool>), $default_value:literal) => ({
if let Ok(_) = input.parse::<Token![=]>() {
let (val, _) = match input.parse::<syn::LitBool>() {
Ok(str) => (str.value(), str.span()),
@ -406,7 +407,7 @@ impl Parse for BindgenAttr {
};
return Ok::<BindgenAttr, syn::Error>(BindgenAttr::$variant(attr_span, Some(val)))
} else {
return Ok(BindgenAttr::$variant(attr_span, Some(true)))
return Ok(BindgenAttr::$variant(attr_span, Some($default_value)))
}
});

View file

@ -862,6 +862,7 @@ impl ConvertToAST for syn::ItemStruct {
} else {
NapiStructKind::None
};
let use_nullable = opts.use_nullable();
for (i, field) in self.fields.iter_mut().enumerate() {
match field.vis {
@ -943,6 +944,7 @@ impl ConvertToAST for syn::ItemStruct {
implement_iterator,
use_custom_finalize: opts.custom_finalize().is_some(),
register_name: get_register_ident(format!("{struct_name}_struct").as_str()),
use_nullable,
}),
})
}

View file

@ -143,6 +143,14 @@ Generated by [AVA](https://avajs.dev).
constructor(width: number, height: number)␊
}␊
export class DefaultUseNullableClass {␊
requiredNumberField: number␊
requiredStringField: string␊
optionalNumberField?: number␊
optionalStringField?: string␊
constructor(requiredNumberField: number, requiredStringField: string, optionalNumberField?: number, optionalStringField?: string)␊
}␊
export class Dog {␊
name: string␊
constructor(name: string)␊
@ -193,6 +201,14 @@ Generated by [AVA](https://avajs.dev).
returnThis(this: this): this␊
}␊
export class NotUseNullableClass {␊
requiredNumberField: number␊
requiredStringField: string␊
optionalNumberField?: number␊
optionalStringField?: string␊
constructor(requiredNumberField: number, requiredStringField: string, optionalNumberField?: number, optionalStringField?: string)␊
}␊
export class NotWritableClass {␊
name: string␊
constructor(name: string)␊
@ -214,6 +230,14 @@ Generated by [AVA](https://avajs.dev).
constructor(orderBy: Array<string>, select: Array<string>, struct: string, where?: string)␊
}␊
export class UseNullableClass {␊
requiredNumberField: number␊
requiredStringField: string␊
nullableNumberField: number | null␊
nullableStringField: string | null␊
constructor(requiredNumberField: number, requiredStringField: string, nullableNumberField: number | null, nullableStringField: string | null)␊
}␊
export class Width {␊
value: number␊
constructor(value: number)␊
@ -372,6 +396,13 @@ Generated by [AVA](https://avajs.dev).
/** This is a const */␊
export const DEFAULT_COST: number␊
export interface DefaultUseNullableStruct {␊
requiredNumberField: number␊
requiredStringField: string␊
optionalNumberField?: number␊
optionalStringField?: string␊
}␊
export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number␊
export function either3(input: string | number | boolean): number␊
@ -465,6 +496,13 @@ Generated by [AVA](https://avajs.dev).
export function mutateTypedArray(input: Float32Array): void␊
export interface NotUseNullableStruct {␊
requiredNumberField: number␊
requiredStringField: string␊
optionalNumberField?: number␊
optionalStringField?: string␊
}␊
export interface Obj {␊
v: string | number␊
}␊
@ -619,6 +657,13 @@ Generated by [AVA](https://avajs.dev).
export function u8ArrayToArray(input: any): Array<number>
export interface UseNullableStruct {␊
requiredNumberField: number␊
requiredStringField: string␊
nullableNumberField: number | null␊
nullableStringField: string | null␊
}␊
export function validateArray(arr: Array<number>): number␊
export function validateBigint(input: bigint): bigint␊

View file

@ -133,6 +133,14 @@ export class CustomFinalize {
constructor(width: number, height: number)
}
export class DefaultUseNullableClass {
requiredNumberField: number
requiredStringField: string
optionalNumberField?: number
optionalStringField?: string
constructor(requiredNumberField: number, requiredStringField: string, optionalNumberField?: number, optionalStringField?: string)
}
export class Dog {
name: string
constructor(name: string)
@ -183,6 +191,14 @@ export class NinjaTurtle {
returnThis(this: this): this
}
export class NotUseNullableClass {
requiredNumberField: number
requiredStringField: string
optionalNumberField?: number
optionalStringField?: string
constructor(requiredNumberField: number, requiredStringField: string, optionalNumberField?: number, optionalStringField?: string)
}
export class NotWritableClass {
name: string
constructor(name: string)
@ -204,6 +220,14 @@ export class Selector {
constructor(orderBy: Array<string>, select: Array<string>, struct: string, where?: string)
}
export class UseNullableClass {
requiredNumberField: number
requiredStringField: string
nullableNumberField: number | null
nullableStringField: string | null
constructor(requiredNumberField: number, requiredStringField: string, nullableNumberField: number | null, nullableStringField: string | null)
}
export class Width {
value: number
constructor(value: number)
@ -362,6 +386,13 @@ export function dateToNumber(input: Date): number
/** This is a const */
export const DEFAULT_COST: number
export interface DefaultUseNullableStruct {
requiredNumberField: number
requiredStringField: string
optionalNumberField?: number
optionalStringField?: string
}
export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number
export function either3(input: string | number | boolean): number
@ -455,6 +486,13 @@ export function mutateExternal(external: ExternalObject<number>, newVal: number)
export function mutateTypedArray(input: Float32Array): void
export interface NotUseNullableStruct {
requiredNumberField: number
requiredStringField: string
optionalNumberField?: number
optionalStringField?: string
}
export interface Obj {
v: string | number
}
@ -609,6 +647,13 @@ export function u64ArrayToArray(input: any): Array<bigint>
export function u8ArrayToArray(input: any): Array<number>
export interface UseNullableStruct {
requiredNumberField: number
requiredStringField: string
nullableNumberField: number | null
nullableStringField: string | null
}
export function validateArray(arr: Array<number>): number
export function validateBigint(input: bigint): bigint

View file

@ -12,3 +12,51 @@ fn return_null() -> Null {
#[napi]
fn return_undefined() -> Undefined {}
#[napi(object, use_nullable = true)]
struct UseNullableStruct {
pub required_number_field: u32,
pub required_string_field: String,
pub nullable_number_field: Option<u32>,
pub nullable_string_field: Option<String>,
}
#[napi(object, use_nullable = false)]
struct NotUseNullableStruct {
pub required_number_field: u32,
pub required_string_field: String,
pub optional_number_field: Option<u32>,
pub optional_string_field: Option<String>,
}
#[napi(object)]
struct DefaultUseNullableStruct {
pub required_number_field: u32,
pub required_string_field: String,
pub optional_number_field: Option<u32>,
pub optional_string_field: Option<String>,
}
#[napi(constructor, use_nullable = true)]
struct UseNullableClass {
pub required_number_field: u32,
pub required_string_field: String,
pub nullable_number_field: Option<u32>,
pub nullable_string_field: Option<String>,
}
#[napi(constructor, use_nullable = false)]
struct NotUseNullableClass {
pub required_number_field: u32,
pub required_string_field: String,
pub optional_number_field: Option<u32>,
pub optional_string_field: Option<String>,
}
#[napi(constructor)]
struct DefaultUseNullableClass {
pub required_number_field: u32,
pub required_string_field: String,
pub optional_number_field: Option<u32>,
pub optional_string_field: Option<String>,
}