feat(napi-derive-backend, napi-derive): add support for string enums (#1551)

This commit is contained in:
Francesco Benedetto 2023-04-03 08:10:58 +02:00 committed by GitHub
parent 71e44be73d
commit 7c4dc2a2bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 132 additions and 72 deletions

View file

@ -1,4 +1,4 @@
use proc_macro2::Ident;
use proc_macro2::{Ident, Literal};
use syn::{Attribute, Expr, Type};
#[derive(Debug, Clone)]
@ -133,10 +133,25 @@ pub struct NapiEnum {
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)]
pub struct NapiEnumVariant {
pub name: Ident,
pub val: i32,
pub val: NapiEnumValue,
pub comments: Vec<String>,
}

View file

@ -29,7 +29,7 @@ impl NapiEnum {
let mut to_napi_branches = vec![];
self.variants.iter().for_each(|v| {
let val = Literal::i32_unsuffixed(v.val);
let val: Literal = (&v.val).into();
let v_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,
napi_val: napi::bindgen_prelude::sys::napi_value
) -> 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!(
e.status,
"Failed to convert napi value into enum `{}`. {}",
@ -76,7 +76,7 @@ impl NapiEnum {
_ => {
Err(napi::bindgen_prelude::error!(
napi::bindgen_prelude::Status::InvalidArg,
"value `{}` does not match any variant of enum `{}`",
"value `{:?}` does not match any variant of enum `{}`",
val,
#name_str
))
@ -94,7 +94,7 @@ impl NapiEnum {
#(#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() {
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! {
{
let name = std::ffi::CStr::from_bytes_with_nul_unchecked(#name_lit.as_bytes());
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 `{}`",
#js_name_lit
)?;

View file

@ -1,5 +1,5 @@
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 {
fn to_type_def(&self) -> Option<TypeDef> {
@ -26,12 +26,11 @@ impl NapiEnum {
.variants
.iter()
.map(|v| {
format!(
"{}{} = {}",
js_doc_from_comments(&v.comments),
v.name,
v.val,
)
let val = match &v.val {
NapiEnumValue::Number(num) => format!("{}", num),
NapiEnumValue::String(string) => format!("'{}'", string),
};
format!("{}{} = {}", js_doc_from_comments(&v.comments), v.name, val)
})
.collect::<Vec<_>>()
.join(",\n ")

View file

@ -38,7 +38,7 @@ pub struct BindgenAttrs {
}
// NOTE: borrowed from wasm-bindgen
// some of them may useless is #[napi] macro
// some of them may useless in #[napi] macro
macro_rules! attrgen {
($mac:ident) => {
$mac! {
@ -65,6 +65,7 @@ macro_rules! attrgen {
(ts_return_type, TsReturnType(Span, String, Span)),
(ts_type, TsType(Span, String, Span)),
(ts_generic_types, TsGenericTypes(Span, String, Span)),
(string_enum, StringEnum(Span)),
// impl later
// (inspectable, Inspectable(Span)),

View file

@ -9,7 +9,7 @@ use attrs::{BindgenAttr, BindgenAttrs};
use convert_case::{Case, Casing};
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,
NapiStructField, NapiStructKind,
};
@ -1124,63 +1124,87 @@ impl ConvertToAST for syn::ItemEnum {
.js_name()
.map_or_else(|| self.ident.to_string(), |(s, _)| s.to_string());
let mut last_variant_val: i32 = -1;
let variants = self
.variants
.iter()
.map(|v| {
match 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",
),
}
let variants = match opts.string_enum() {
Some(_) => self
.variants
.iter()
.map(|v| {
if !matches!(v.fields, syn::Fields::Unit) {
bail_span!(v.fields, "Structured enum is not supported in #[napi]")
}
None => last_variant_val + 1,
};
last_variant_val = val;
Ok(NapiEnumVariant {
name: v.ident.clone(),
val,
comments: extract_doc_comments(&v.attrs),
if matches!(&v.discriminant, Some((_, _))) {
bail_span!(
v.fields,
"Literal values are not supported with string enum in #[napi]"
)
}
Ok(NapiEnumVariant {
name: v.ident.clone(),
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 {
item: NapiItem::Enum(NapiEnum {

View file

@ -98,6 +98,11 @@ Generated by [AVA](https://avajs.dev).
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. */␊
export const enum CustomNumEnum {␊
One = 1,␊

View file

@ -87,6 +87,11 @@ export const enum Kind {
}
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. */
export const enum CustomNumEnum {

View file

@ -14,6 +14,13 @@ pub enum Kind {
#[napi]
pub enum Empty {}
#[napi(string_enum)]
pub enum Status {
Pristine,
Loading,
Ready,
}
/// You could break the step and for an new continuous value.
#[napi]
pub enum CustomNumEnum {