diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index b907775e..a1ee4424 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -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 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, } diff --git a/crates/backend/src/codegen/enum.rs b/crates/backend/src/codegen/enum.rs index 27dd3346..0a7137c2 100644 --- a/crates/backend/src/codegen/enum.rs +++ b/crates/backend/src/codegen/enum.rs @@ -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 { - 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 )?; diff --git a/crates/backend/src/typegen/enum.rs b/crates/backend/src/typegen/enum.rs index 6f3c1efe..cdd03780 100644 --- a/crates/backend/src/typegen/enum.rs +++ b/crates/backend/src/typegen/enum.rs @@ -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 { @@ -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::>() .join(",\n ") diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index a0fcdd94..19b4a110 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -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)), diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index f4b1a2b6..86afc1f0 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -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::() { - 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::>>()?; + .collect::>>()?, + 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::() { + 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::>>()? + } + }; Ok(Napi { item: NapiItem::Enum(NapiEnum { diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 8c68405a..e978724c 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -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,␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index e2499777..abcd4363 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 2ad678be..aba4722f 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -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 { diff --git a/examples/napi/src/enum.rs b/examples/napi/src/enum.rs index 58e1cd12..a2e72a18 100644 --- a/examples/napi/src/enum.rs +++ b/examples/napi/src/enum.rs @@ -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 {