feat(napi-derive-backend, napi-derive): add support for string enums (#1551)
This commit is contained in:
parent
71e44be73d
commit
7c4dc2a2bd
9 changed files with 132 additions and 72 deletions
|
@ -1,4 +1,4 @@
|
||||||
use proc_macro2::Ident;
|
use proc_macro2::{Ident, Literal};
|
||||||
use syn::{Attribute, Expr, Type};
|
use syn::{Attribute, Expr, Type};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -133,10 +133,25 @@ pub struct NapiEnum {
|
||||||
pub skip_typescript: bool,
|
pub skip_typescript: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum NapiEnumValue {
|
||||||
|
String(String),
|
||||||
|
Number(i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Literal> for &NapiEnumValue {
|
||||||
|
fn into(self) -> Literal {
|
||||||
|
match self {
|
||||||
|
NapiEnumValue::String(string) => Literal::string(string),
|
||||||
|
NapiEnumValue::Number(number) => Literal::i32_unsuffixed(number.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NapiEnumVariant {
|
pub struct NapiEnumVariant {
|
||||||
pub name: Ident,
|
pub name: Ident,
|
||||||
pub val: i32,
|
pub val: NapiEnumValue,
|
||||||
pub comments: Vec<String>,
|
pub comments: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ impl NapiEnum {
|
||||||
let mut to_napi_branches = vec![];
|
let mut to_napi_branches = vec![];
|
||||||
|
|
||||||
self.variants.iter().for_each(|v| {
|
self.variants.iter().for_each(|v| {
|
||||||
let val = Literal::i32_unsuffixed(v.val);
|
let val: Literal = (&v.val).into();
|
||||||
let v_name = &v.name;
|
let v_name = &v.name;
|
||||||
|
|
||||||
from_napi_branches.push(quote! { #val => Ok(#name::#v_name) });
|
from_napi_branches.push(quote! { #val => Ok(#name::#v_name) });
|
||||||
|
@ -62,7 +62,7 @@ impl NapiEnum {
|
||||||
env: napi::bindgen_prelude::sys::napi_env,
|
env: napi::bindgen_prelude::sys::napi_env,
|
||||||
napi_val: napi::bindgen_prelude::sys::napi_value
|
napi_val: napi::bindgen_prelude::sys::napi_value
|
||||||
) -> napi::bindgen_prelude::Result<Self> {
|
) -> napi::bindgen_prelude::Result<Self> {
|
||||||
let val = i32::from_napi_value(env, napi_val).map_err(|e| {
|
let val = FromNapiValue::from_napi_value(env, napi_val).map_err(|e| {
|
||||||
napi::bindgen_prelude::error!(
|
napi::bindgen_prelude::error!(
|
||||||
e.status,
|
e.status,
|
||||||
"Failed to convert napi value into enum `{}`. {}",
|
"Failed to convert napi value into enum `{}`. {}",
|
||||||
|
@ -76,7 +76,7 @@ impl NapiEnum {
|
||||||
_ => {
|
_ => {
|
||||||
Err(napi::bindgen_prelude::error!(
|
Err(napi::bindgen_prelude::error!(
|
||||||
napi::bindgen_prelude::Status::InvalidArg,
|
napi::bindgen_prelude::Status::InvalidArg,
|
||||||
"value `{}` does not match any variant of enum `{}`",
|
"value `{:?}` does not match any variant of enum `{}`",
|
||||||
val,
|
val,
|
||||||
#name_str
|
#name_str
|
||||||
))
|
))
|
||||||
|
@ -94,7 +94,7 @@ impl NapiEnum {
|
||||||
#(#to_napi_branches,)*
|
#(#to_napi_branches,)*
|
||||||
};
|
};
|
||||||
|
|
||||||
i32::to_napi_value(env, val)
|
ToNapiValue::to_napi_value(env, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,13 +109,17 @@ impl NapiEnum {
|
||||||
|
|
||||||
for variant in self.variants.iter() {
|
for variant in self.variants.iter() {
|
||||||
let name_lit = Literal::string(&format!("{}\0", variant.name));
|
let name_lit = Literal::string(&format!("{}\0", variant.name));
|
||||||
let val_lit = Literal::i32_unsuffixed(variant.val);
|
let val_lit: Literal = (&variant.val).into();
|
||||||
|
|
||||||
define_properties.push(quote! {
|
define_properties.push(quote! {
|
||||||
{
|
{
|
||||||
let name = std::ffi::CStr::from_bytes_with_nul_unchecked(#name_lit.as_bytes());
|
let name = std::ffi::CStr::from_bytes_with_nul_unchecked(#name_lit.as_bytes());
|
||||||
napi::bindgen_prelude::check_status!(
|
napi::bindgen_prelude::check_status!(
|
||||||
napi::bindgen_prelude::sys::napi_set_named_property(env, obj_ptr, name.as_ptr(), i32::to_napi_value(env, #val_lit)?),
|
napi::bindgen_prelude::sys::napi_set_named_property(
|
||||||
|
env,
|
||||||
|
obj_ptr, name.as_ptr(),
|
||||||
|
ToNapiValue::to_napi_value(env, #val_lit)?
|
||||||
|
),
|
||||||
"Failed to defined enum `{}`",
|
"Failed to defined enum `{}`",
|
||||||
#js_name_lit
|
#js_name_lit
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{add_alias, ToTypeDef, TypeDef};
|
use super::{add_alias, ToTypeDef, TypeDef};
|
||||||
use crate::{js_doc_from_comments, NapiEnum};
|
use crate::{js_doc_from_comments, NapiEnum, NapiEnumValue};
|
||||||
|
|
||||||
impl ToTypeDef for NapiEnum {
|
impl ToTypeDef for NapiEnum {
|
||||||
fn to_type_def(&self) -> Option<TypeDef> {
|
fn to_type_def(&self) -> Option<TypeDef> {
|
||||||
|
@ -26,12 +26,11 @@ impl NapiEnum {
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
format!(
|
let val = match &v.val {
|
||||||
"{}{} = {}",
|
NapiEnumValue::Number(num) => format!("{}", num),
|
||||||
js_doc_from_comments(&v.comments),
|
NapiEnumValue::String(string) => format!("'{}'", string),
|
||||||
v.name,
|
};
|
||||||
v.val,
|
format!("{}{} = {}", js_doc_from_comments(&v.comments), v.name, val)
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(",\n ")
|
.join(",\n ")
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub struct BindgenAttrs {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: borrowed from wasm-bindgen
|
// NOTE: borrowed from wasm-bindgen
|
||||||
// some of them may useless is #[napi] macro
|
// some of them may useless in #[napi] macro
|
||||||
macro_rules! attrgen {
|
macro_rules! attrgen {
|
||||||
($mac:ident) => {
|
($mac:ident) => {
|
||||||
$mac! {
|
$mac! {
|
||||||
|
@ -65,6 +65,7 @@ macro_rules! attrgen {
|
||||||
(ts_return_type, TsReturnType(Span, String, Span)),
|
(ts_return_type, TsReturnType(Span, String, Span)),
|
||||||
(ts_type, TsType(Span, String, Span)),
|
(ts_type, TsType(Span, String, Span)),
|
||||||
(ts_generic_types, TsGenericTypes(Span, String, Span)),
|
(ts_generic_types, TsGenericTypes(Span, String, Span)),
|
||||||
|
(string_enum, StringEnum(Span)),
|
||||||
|
|
||||||
// impl later
|
// impl later
|
||||||
// (inspectable, Inspectable(Span)),
|
// (inspectable, Inspectable(Span)),
|
||||||
|
|
|
@ -9,7 +9,7 @@ use attrs::{BindgenAttr, BindgenAttrs};
|
||||||
|
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use napi_derive_backend::{
|
use napi_derive_backend::{
|
||||||
BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiConst, NapiEnum,
|
BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiConst, NapiEnum, NapiEnumValue,
|
||||||
NapiEnumVariant, NapiFn, NapiFnArg, NapiFnArgKind, NapiImpl, NapiItem, NapiStruct,
|
NapiEnumVariant, NapiFn, NapiFnArg, NapiFnArgKind, NapiImpl, NapiItem, NapiStruct,
|
||||||
NapiStructField, NapiStructKind,
|
NapiStructField, NapiStructKind,
|
||||||
};
|
};
|
||||||
|
@ -1124,63 +1124,87 @@ impl ConvertToAST for syn::ItemEnum {
|
||||||
.js_name()
|
.js_name()
|
||||||
.map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string());
|
.map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string());
|
||||||
|
|
||||||
let mut last_variant_val: i32 = -1;
|
let variants = match opts.string_enum() {
|
||||||
let variants = self
|
Some(_) => self
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
match v.fields {
|
if !matches!(v.fields, syn::Fields::Unit) {
|
||||||
syn::Fields::Unit => {}
|
bail_span!(v.fields, "Structured enum is not supported in #[napi]")
|
||||||
_ => bail_span!(v.fields, "Structured enum is not supported in #[napi]"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let val = match &v.discriminant {
|
|
||||||
Some((_, expr)) => {
|
|
||||||
let mut symbol = 1;
|
|
||||||
let mut inner_expr = get_expr(expr);
|
|
||||||
if let syn::Expr::Unary(syn::ExprUnary {
|
|
||||||
attrs: _,
|
|
||||||
op: syn::UnOp::Neg(_),
|
|
||||||
expr,
|
|
||||||
}) = inner_expr
|
|
||||||
{
|
|
||||||
symbol = -1;
|
|
||||||
inner_expr = expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
match inner_expr {
|
|
||||||
syn::Expr::Lit(syn::ExprLit {
|
|
||||||
attrs: _,
|
|
||||||
lit: syn::Lit::Int(int_lit),
|
|
||||||
}) => match int_lit.base10_digits().parse::<i32>() {
|
|
||||||
Ok(v) => symbol * v,
|
|
||||||
Err(_) => {
|
|
||||||
bail_span!(
|
|
||||||
int_lit,
|
|
||||||
"enums with #[wasm_bindgen] can only support \
|
|
||||||
numbers that can be represented as i32",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => bail_span!(
|
|
||||||
expr,
|
|
||||||
"enums with #[wasm_bindgen] may only have \
|
|
||||||
number literal values",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => last_variant_val + 1,
|
if matches!(&v.discriminant, Some((_, _))) {
|
||||||
};
|
bail_span!(
|
||||||
|
v.fields,
|
||||||
last_variant_val = val;
|
"Literal values are not supported with string enum in #[napi]"
|
||||||
|
)
|
||||||
Ok(NapiEnumVariant {
|
}
|
||||||
name: v.ident.clone(),
|
Ok(NapiEnumVariant {
|
||||||
val,
|
name: v.ident.clone(),
|
||||||
comments: extract_doc_comments(&v.attrs),
|
val: NapiEnumValue::String(v.ident.to_string()),
|
||||||
|
comments: extract_doc_comments(&v.attrs),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
.collect::<BindgenResult<Vec<NapiEnumVariant>>>()?,
|
||||||
.collect::<BindgenResult<Vec<NapiEnumVariant>>>()?;
|
None => {
|
||||||
|
let mut last_variant_val: i32 = -1;
|
||||||
|
|
||||||
|
self
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
if !matches!(v.fields, syn::Fields::Unit) {
|
||||||
|
bail_span!(v.fields, "Structured enum is not supported in #[napi]")
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = match &v.discriminant {
|
||||||
|
Some((_, expr)) => {
|
||||||
|
let mut symbol = 1;
|
||||||
|
let mut inner_expr = get_expr(expr);
|
||||||
|
if let syn::Expr::Unary(syn::ExprUnary {
|
||||||
|
attrs: _,
|
||||||
|
op: syn::UnOp::Neg(_),
|
||||||
|
expr,
|
||||||
|
}) = inner_expr
|
||||||
|
{
|
||||||
|
symbol = -1;
|
||||||
|
inner_expr = expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
match inner_expr {
|
||||||
|
syn::Expr::Lit(syn::ExprLit {
|
||||||
|
attrs: _,
|
||||||
|
lit: syn::Lit::Int(int_lit),
|
||||||
|
}) => match int_lit.base10_digits().parse::<i32>() {
|
||||||
|
Ok(v) => symbol * v,
|
||||||
|
Err(_) => {
|
||||||
|
bail_span!(
|
||||||
|
int_lit,
|
||||||
|
"enums with #[wasm_bindgen] can only support \
|
||||||
|
numbers that can be represented as i32",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => bail_span!(
|
||||||
|
expr,
|
||||||
|
"enums with #[wasm_bindgen] may only have \
|
||||||
|
number literal values",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => last_variant_val + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
last_variant_val = val;
|
||||||
|
|
||||||
|
Ok(NapiEnumVariant {
|
||||||
|
name: v.ident.clone(),
|
||||||
|
val: NapiEnumValue::Number(val),
|
||||||
|
comments: extract_doc_comments(&v.attrs),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<BindgenResult<Vec<NapiEnumVariant>>>()?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Napi {
|
Ok(Napi {
|
||||||
item: NapiItem::Enum(NapiEnum {
|
item: NapiItem::Enum(NapiEnum {
|
||||||
|
|
|
@ -98,6 +98,11 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export const enum Empty {␊
|
export const enum Empty {␊
|
||||||
␊
|
␊
|
||||||
}␊
|
}␊
|
||||||
|
export const enum Status {␊
|
||||||
|
Pristine = 'Pristine',␊
|
||||||
|
Loading = 'Loading',␊
|
||||||
|
Ready = 'Ready'␊
|
||||||
|
}␊
|
||||||
/** You could break the step and for an new continuous value. */␊
|
/** You could break the step and for an new continuous value. */␊
|
||||||
export const enum CustomNumEnum {␊
|
export const enum CustomNumEnum {␊
|
||||||
One = 1,␊
|
One = 1,␊
|
||||||
|
|
Binary file not shown.
5
examples/napi/index.d.ts
vendored
5
examples/napi/index.d.ts
vendored
|
@ -87,6 +87,11 @@ export const enum Kind {
|
||||||
}
|
}
|
||||||
export const enum Empty {
|
export const enum Empty {
|
||||||
|
|
||||||
|
}
|
||||||
|
export const enum Status {
|
||||||
|
Pristine = 'Pristine',
|
||||||
|
Loading = 'Loading',
|
||||||
|
Ready = 'Ready'
|
||||||
}
|
}
|
||||||
/** You could break the step and for an new continuous value. */
|
/** You could break the step and for an new continuous value. */
|
||||||
export const enum CustomNumEnum {
|
export const enum CustomNumEnum {
|
||||||
|
|
|
@ -14,6 +14,13 @@ pub enum Kind {
|
||||||
#[napi]
|
#[napi]
|
||||||
pub enum Empty {}
|
pub enum Empty {}
|
||||||
|
|
||||||
|
#[napi(string_enum)]
|
||||||
|
pub enum Status {
|
||||||
|
Pristine,
|
||||||
|
Loading,
|
||||||
|
Ready,
|
||||||
|
}
|
||||||
|
|
||||||
/// You could break the step and for an new continuous value.
|
/// You could break the step and for an new continuous value.
|
||||||
#[napi]
|
#[napi]
|
||||||
pub enum CustomNumEnum {
|
pub enum CustomNumEnum {
|
||||||
|
|
Loading…
Reference in a new issue