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:
parent
43415251b8
commit
ebe97257a6
9 changed files with 190 additions and 25 deletions
|
@ -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)]
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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␊
|
||||
|
|
Binary file not shown.
45
examples/napi/index.d.ts
vendored
45
examples/napi/index.d.ts
vendored
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue