diff --git a/Cargo.lock b/Cargo.lock index 4035c4fdf..2c9530715 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -676,6 +676,7 @@ dependencies = [ "futures", "indexmap", "libc", + "num-bigint", "parking_lot", "paste", "percent-encoding", diff --git a/core/Cargo.toml b/core/Cargo.toml index fc061ad5c..d67d7f857 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -42,6 +42,7 @@ deno_unsync.workspace = true futures.workspace = true indexmap.workspace = true libc.workspace = true +num-bigint.workspace = true parking_lot.workspace = true percent-encoding.workspace = true pin-project.workspace = true diff --git a/core/convert.rs b/core/convert.rs index 942dd8892..1b3016993 100644 --- a/core/convert.rs +++ b/core/convert.rs @@ -325,6 +325,7 @@ impl_number_types!( u8, i8, u16, i16, u32, i32, u64, i64, usize, isize, f32, f64 ); +#[derive(Debug)] pub struct BigInt { pub sign_bit: bool, pub words: Vec, @@ -360,6 +361,37 @@ impl<'s> FromV8<'s> for BigInt { } } +impl From for BigInt { + fn from(big_int: num_bigint::BigInt) -> Self { + let (sign, words) = big_int.to_u64_digits(); + Self { + sign_bit: sign == num_bigint::Sign::Minus, + words, + } + } +} + +impl From for num_bigint::BigInt { + fn from(big_int: BigInt) -> Self { + // SAFETY: Because the alignment of u64 is 8, the alignment of u32 is 4, and + // the size of u64 is 8, the size of u32 is 4, the alignment of u32 is a + // factor of the alignment of u64, and the size of u32 is a factor of the + // size of u64, we can safely transmute the slice of u64 to a slice of u32. + let (prefix, slice, suffix) = unsafe { big_int.words.align_to::() }; + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + assert_eq!(slice.len(), big_int.words.len() * 2); + Self::from_slice( + if big_int.sign_bit { + num_bigint::Sign::Minus + } else { + num_bigint::Sign::Plus + }, + slice, + ) + } +} + impl<'s> ToV8<'s> for bool { type Error = Infallible; #[inline] @@ -424,6 +456,17 @@ impl<'s> ToV8<'s> for &'static str { } } +impl<'s> ToV8<'s> for Box { + type Error = Infallible; + #[inline] + fn to_v8<'i>( + self, + scope: &mut v8::PinScope<'s, 'i>, + ) -> Result, Self::Error> { + Ok(v8::String::new(scope, &self).unwrap().into()) // TODO + } +} + const USIZE2X: usize = std::mem::size_of::() * 2; #[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct ByteString(SmallVec<[u8; USIZE2X]>); diff --git a/ops/conversion/mod.rs b/ops/conversion/mod.rs index e3c5f1964..8955eb7f9 100644 --- a/ops/conversion/mod.rs +++ b/ops/conversion/mod.rs @@ -14,6 +14,9 @@ pub mod to_v8; mod kw { syn::custom_keyword!(rename); syn::custom_keyword!(serde); + syn::custom_keyword!(tag); + syn::custom_keyword!(untagged); + syn::custom_keyword!(content); } #[allow(dead_code)] diff --git a/ops/conversion/to_v8/enum.rs b/ops/conversion/to_v8/enum.rs new file mode 100644 index 000000000..827d63767 --- /dev/null +++ b/ops/conversion/to_v8/enum.rs @@ -0,0 +1,409 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use crate::conversion::kw as shared_kw; +use proc_macro2::Ident; +use proc_macro2::Span; +use proc_macro2::TokenStream; +use quote::format_ident; +use quote::quote; +use stringcase::camel_case; +use syn::Attribute; +use syn::DataEnum; +use syn::DataStruct; +use syn::Error; +use syn::Fields; +use syn::LitStr; +use syn::Token; +use syn::Variant; +use syn::ext::IdentExt; +use syn::parse::Parse; +use syn::parse::ParseStream; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; + +pub fn get_body( + span: Span, + attributes: Vec, + data: DataEnum, +) -> Result { + let mode = EnumMode::from_attributes(span, attributes)?; + + let variants = data + .variants + .into_iter() + .map(|variant| { + let variant_span = variant.span(); + let variant_attrs = EnumVariantAttribute::from_variant(&variant)?; + let variant_ident = variant.ident; + + let tag_name = variant_attrs + .rename + .unwrap_or_else(|| camel_case(&variant_ident.unraw().to_string())); + let tag_value = crate::get_internalized_string(Ident::new( + &tag_name, + variant_ident.span(), + ))?; + + if variant.fields == Fields::Unit { + let tag = match &mode { + EnumMode::ExternallyTagged => quote! { + Ok(#tag_value) + }, + EnumMode::InternallyTagged { tag } | EnumMode::AdjacentlyTagged { tag, .. } => { + quote! { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[#tag]; + let __values = &[#tag_value]; + + Ok(::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __values, + ).into()) + } + } + EnumMode::Untagged => { + quote! { + Ok(::deno_core::v8::null(__scope).into()) + } + } + }; + + Ok(quote! { + Self::#variant_ident => { + #tag + } + }) + } else { + let fields = super::destruct_fields(&variant.fields)?; + + let conversion = if variant_attrs.serde { + get_serde_variant_conversion(variant_span, variant.fields)? + } else { + super::r#struct::get_body( + variant_span, + DataStruct { + struct_token: Default::default(), + fields: variant.fields, + semi_token: None, + }, + )? + }; + + let tag = match &mode { + EnumMode::ExternallyTagged => { + quote! { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[#tag_value]; + let __converters = &[__body]; + + Ok(::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ).into()) + } + } + EnumMode::InternallyTagged { tag } => { + quote! { + if Ok(__obj_body) = __body.try_cast::<::deno_core::v8::Object>() { + let __tag_key = #tag; + let __tag_value = #tag_value; + + __obj_body.set(__scope, __tag_key, __tag_value); + + Ok(__obj_body.into()) + } else { + panic!("cannot use non-object value with an internally tag enum"); + } + } + } + EnumMode::AdjacentlyTagged { tag, content } => { + quote! { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[#tag, #content]; + let __converters = &[#tag_value, __body]; + + Ok(::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ).into()) + } + } + EnumMode::Untagged => { + quote! { + Ok(__body) + } + } + }; + + Ok(quote! { + Self::#variant_ident #fields => { + let __body = { #conversion }?; + #tag + } + }) + } + }) + .collect::, Error>>()?; + + let impl_body = quote! { + match self { + #(#variants),* + } + }; + + Ok(impl_body) +} + +fn get_serde_variant_conversion( + span: Span, + fields: Fields, +) -> Result { + match fields { + Fields::Named(named) => { + let mut field_names = Vec::with_capacity(named.named.len()); + let mut field_strs = Vec::with_capacity(named.named.len()); + + for field in named.named { + let field = super::r#struct::StructField::try_from(field)?; + + field_names.push(field.name); + field_strs.push(field.js_name); + } + + Ok(quote! { + let __obj = ::deno_core::v8::Object::new(__scope); + #( + let __key = ::deno_core::v8::String::new(__scope, stringify!(#field_strs)).unwrap().into(); + let __val = ::deno_core::serde_v8::to_v8(__scope, #field_names).map_err(::deno_error::JsErrorBox::from_err)?; + __obj.set(__scope, __key, __val); + )* + Ok::<_, ::deno_error::JsErrorBox>(__obj.into()) + }) + } + Fields::Unnamed(unnamed) => { + if unnamed.unnamed.len() == 1 { + Ok(quote! { + Ok::<_, ::deno_error::JsErrorBox>( + ::deno_core::serde_v8::to_v8(__scope, __0) + .map_err(::deno_error::JsErrorBox::from_err)? + ) + }) + } else { + let len = unnamed.unnamed.len(); + let field_idents: Vec<_> = unnamed + .unnamed + .into_iter() + .enumerate() + .map(|(i, field)| format_ident!("__{}", i, span = field.span())) + .collect(); + let indices: Vec<_> = (0..len as u32).collect(); + + Ok(quote! { + let __arr = ::deno_core::v8::Array::new(__scope, #len as i32); + #( + let __val = ::deno_core::serde_v8::to_v8(__scope, #field_idents) + .map_err(::deno_error::JsErrorBox::from_err)?; + __arr.set_index(__scope, #indices, __val); + )* + Ok::<_, ::deno_error::JsErrorBox>(__arr.into()) + }) + } + } + Fields::Unit => Err(Error::new(span, "Cannot use serde on unit variant")), + } +} + +#[derive(Default)] +enum EnumMode { + #[default] + ExternallyTagged, + InternallyTagged { + tag: TokenStream, + }, + AdjacentlyTagged { + tag: TokenStream, + content: TokenStream, + }, + Untagged, +} + +impl EnumMode { + fn from_attributes(span: Span, attrs: Vec) -> Result { + let mut tag: Option = None; + let mut content: Option = None; + let mut untagged = false; + + for attr in attrs { + if attr.path().is_ident("to_v8") { + let list = attr.meta.require_list()?; + let args = list.parse_args_with( + Punctuated::::parse_terminated, + )?; + + for arg in args { + match arg { + EnumModeArgument::Tag { value, .. } => tag = Some(value.value()), + EnumModeArgument::Content { value, .. } => { + content = Some(value.value()) + } + EnumModeArgument::Untagged { .. } => untagged = true, + } + } + } + } + + if untagged { + if tag.is_some() || content.is_some() { + return Err(Error::new( + Span::call_site(), + "Cannot combine `untagged` with `tag` or `content`", + )); + } + return Ok(EnumMode::Untagged); + } + + match (tag, content) { + (None, None) => Ok(EnumMode::ExternallyTagged), + (Some(tag), None) => Ok(EnumMode::InternallyTagged { + tag: crate::get_internalized_string(Ident::new(&tag, span))?, + }), + (Some(tag), Some(content)) => Ok(EnumMode::AdjacentlyTagged { + tag: crate::get_internalized_string(Ident::new(&tag, span))?, + content: crate::get_internalized_string(Ident::new(&content, span))?, + }), + (None, Some(_)) => Err(Error::new( + Span::call_site(), + "`content` requires `tag` to be specified", + )), + } + } +} + +#[allow(dead_code)] +enum EnumModeArgument { + Tag { + name_token: shared_kw::tag, + eq_token: Token![=], + value: LitStr, + }, + Content { + name_token: shared_kw::content, + eq_token: Token![=], + value: LitStr, + }, + Untagged { + name_token: shared_kw::untagged, + }, +} + +impl Parse for EnumModeArgument { + fn parse(input: ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(shared_kw::tag) { + Ok(Self::Tag { + name_token: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } else if lookahead.peek(shared_kw::content) { + Ok(Self::Content { + name_token: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } else if lookahead.peek(shared_kw::untagged) { + Ok(Self::Untagged { + name_token: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } +} + +struct EnumVariantAttribute { + rename: Option, + serde: bool, +} + +impl EnumVariantAttribute { + fn from_variant(variant: &Variant) -> syn::Result { + let mut rename: Option = None; + let mut serde = false; + + for attr in &variant.attrs { + if attr.path().is_ident("v8") { + let list = attr.meta.require_list()?; + let args = list.parse_args_with( + Punctuated::::parse_terminated, + )?; + + for arg in args { + match arg { + EnumVariantArgument::Rename { value, .. } => { + rename = Some(value.value()) + } + EnumVariantArgument::Serde { .. } => serde = true, + } + } + } + + if attr.path().is_ident("to_v8") { + let list = attr.meta.require_list()?; + let args = list.parse_args_with( + Punctuated::::parse_terminated, + )?; + + for arg in args { + match arg { + EnumVariantArgument::Rename { value, .. } => { + rename = Some(value.value()) + } + EnumVariantArgument::Serde { .. } => serde = true, + } + } + } + } + + Ok(Self { rename, serde }) + } +} + +#[allow(dead_code)] +enum EnumVariantArgument { + Rename { + name_token: shared_kw::rename, + eq_token: Token![=], + value: LitStr, + }, + Serde { + name_token: shared_kw::serde, + }, +} + +impl Parse for EnumVariantArgument { + fn parse(input: ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(shared_kw::rename) { + Ok(Self::Rename { + name_token: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } else if lookahead.peek(shared_kw::serde) { + Ok(Self::Serde { + name_token: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } +} diff --git a/ops/conversion/to_v8/mod.rs b/ops/conversion/to_v8/mod.rs index e679195e9..a52367286 100644 --- a/ops/conversion/to_v8/mod.rs +++ b/ops/conversion/to_v8/mod.rs @@ -1,15 +1,16 @@ // Copyright 2018-2025 the Deno authors. MIT license. +mod r#enum; mod r#struct; use proc_macro2::TokenStream; -use quote::quote; use quote::{ToTokens, quote_spanned}; -use syn::Data; +use quote::{format_ident, quote}; use syn::DeriveInput; use syn::Error; use syn::parse2; use syn::spanned::Spanned; +use syn::{Data, Fields}; pub fn to_v8(item: TokenStream) -> Result { let input = parse2::(item)?; @@ -17,18 +18,65 @@ pub fn to_v8(item: TokenStream) -> Result { let ident = input.ident; let out = match input.data { - Data::Struct(data) => create_impl(ident, r#struct::get_body(span, data)?), - Data::Enum(_) => return Err(Error::new(span, "Enums are not supported")), + Data::Struct(data) => { + let fields = destruct_fields(&data.fields)?; + let body = r#struct::get_body(span, data)?; + + create_impl( + ident, + quote! { + let Self #fields = self; + #body + }, + ) + } + Data::Enum(data) => { + create_impl(ident, r#enum::get_body(span, input.attrs, data)?) + } Data::Union(_) => return Err(Error::new(span, "Unions are not supported")), }; Ok(out) } -fn convert_or_serde( +fn destruct_fields(fields: &Fields) -> Result { + match fields { + Fields::Named { 0: named } => { + let fields = named + .named + .iter() + .map(|field| field.ident.as_ref().unwrap()); + + Ok(quote! { + { + #(#fields),* + } + }) + } + Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().enumerate().map(|(i, field)| { + let idx = syn::Index::from(i); + let ident = format_ident!("__{i}", span = field.span()); + quote!(#idx: #ident) + }); + + Ok(quote! { + { + #(#fields),* + } + }) + } + Fields::Unit => Err(Error::new( + fields.span(), + "Unit structs cannot be destructured", + )), + } +} + +fn convert_or_serde( serde: bool, span: proc_macro2::Span, - value: TokenStream, + value: T, ) -> TokenStream { if serde { quote_spanned! { span => diff --git a/ops/conversion/to_v8/struct.rs b/ops/conversion/to_v8/struct.rs index b992d2d54..ca091d31c 100644 --- a/ops/conversion/to_v8/struct.rs +++ b/ops/conversion/to_v8/struct.rs @@ -5,7 +5,7 @@ use crate::conversion::to_v8::convert_or_serde; use proc_macro2::Ident; use proc_macro2::Span; use proc_macro2::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::DataStruct; use syn::Error; use syn::Field; @@ -35,11 +35,10 @@ pub fn get_body(span: Span, data: DataStruct) -> Result { for field in fields { names.push(crate::get_internalized_string(field.js_name)?); - let field_name = field.name; converters.push(convert_or_serde( field.serde, field.ty.span(), - quote!(self.#field_name), + field.name, )); } @@ -50,7 +49,7 @@ pub fn get_body(span: Span, data: DataStruct) -> Result { *]; let __converters = &[#(#converters),*]; - Ok(::deno_core::v8::Object::with_prototype_and_properties( + Ok::<_, ::deno_error::JsErrorBox>(::deno_core::v8::Object::with_prototype_and_properties( __scope, __null, __keys, @@ -70,21 +69,20 @@ pub fn get_body(span: Span, data: DataStruct) -> Result { let value = if fields.len() == 1 { let field = fields.first().unwrap(); - let converter = - convert_or_serde(field.serde, field.span, quote!(self.0)); - quote!(Ok(#converter)) + let converter = convert_or_serde(field.serde, field.span, quote!(__0)); + quote!(Ok::<_, ::deno_error::JsErrorBox>(#converter)) } else { let fields = fields .into_iter() .map(|field| { - let i = syn::Index::from(field.i); - convert_or_serde(field.serde, field.span, quote!(self.#i)) + let i = format_ident!("__{}", field.i, span = field.span); + convert_or_serde(field.serde, field.span, i) }) .collect::>(); quote! { let __value = &[#(#fields),*]; - Ok(::deno_core::v8::Array::new_with_elements(__scope, __value).into()) + Ok::<_, ::deno_error::JsErrorBox>(::deno_core::v8::Array::new_with_elements(__scope, __value).into()) } }; @@ -96,11 +94,11 @@ pub fn get_body(span: Span, data: DataStruct) -> Result { } } -struct StructField { - name: Ident, +pub struct StructField { + pub name: Ident, serde: bool, ty: Type, - js_name: Ident, + pub js_name: Ident, } impl TryFrom for StructField { @@ -145,7 +143,7 @@ impl TryFrom for StructField { } #[allow(dead_code)] -enum StructFieldArgument { +pub(crate) enum StructFieldArgument { Rename { name_token: shared_kw::rename, eq_token: Token![=], diff --git a/ops/conversion/to_v8/test_cases/enum.out b/ops/conversion/to_v8/test_cases/enum.out new file mode 100644 index 000000000..69f95342e --- /dev/null +++ b/ops/conversion/to_v8/test_cases/enum.out @@ -0,0 +1,413 @@ +impl<'a> ::deno_core::convert::ToV8<'a> for SimpleEnum { + type Error = ::deno_error::JsErrorBox; + fn to_v8<'i>( + self, + __scope: &mut ::deno_core::v8::PinScope<'a, 'i>, + ) -> Result<::deno_core::v8::Local<'a, ::deno_core::v8::Value>, Self::Error> { + match self { + Self::VariantA => { + Ok( + ::deno_core::v8::String::new_from_one_byte( + __scope, + "variantA".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ) + } + Self::VariantB => { + Ok( + ::deno_core::v8::String::new_from_one_byte( + __scope, + "customName".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ) + } + Self::VariantC { field } => { + let __body = { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "field".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[ + ::deno_core::convert::ToV8::to_v8(field, __scope) + .map_err(::deno_error::JsErrorBox::from_err)?, + ]; + Ok::< + _, + ::deno_error::JsErrorBox, + >( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + }?; + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "variantC".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[__body]; + Ok( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + } + Self::VariantD { value } => { + let __body = { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "value".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[ + ::deno_core::convert::ToV8::to_v8(value, __scope) + .map_err(::deno_error::JsErrorBox::from_err)?, + ]; + Ok::< + _, + ::deno_error::JsErrorBox, + >( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + }?; + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "renamedWithFields".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[__body]; + Ok( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + } + } + } +} + +impl<'a> ::deno_core::convert::ToV8<'a> for InternallyTaggedEnum { + type Error = ::deno_error::JsErrorBox; + fn to_v8<'i>( + self, + __scope: &mut ::deno_core::v8::PinScope<'a, 'i>, + ) -> Result<::deno_core::v8::Local<'a, ::deno_core::v8::Value>, Self::Error> { + match self { + Self::A => { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "type".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __values = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "custom_a".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + Ok( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __values, + ) + .into(), + ) + } + Self::B { data } => { + let __body = { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "data".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[ + ::deno_core::convert::ToV8::to_v8(data, __scope) + .map_err(::deno_error::JsErrorBox::from_err)?, + ]; + Ok::< + _, + ::deno_error::JsErrorBox, + >( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + }?; + if Ok(__obj_body) = __body.try_cast::<::deno_core::v8::Object>() { + let __tag_key = ::deno_core::v8::String::new_from_one_byte( + __scope, + "type".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(); + let __tag_value = ::deno_core::v8::String::new_from_one_byte( + __scope, + "b".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(); + __obj_body.set(__scope, __tag_key, __tag_value); + Ok(__obj_body.into()) + } else { + panic!("cannot use non-object value with an internally tag enum"); + } + } + } + } +} + +impl<'a> ::deno_core::convert::ToV8<'a> for AdjacentlyTaggedEnum { + type Error = ::deno_error::JsErrorBox; + fn to_v8<'i>( + self, + __scope: &mut ::deno_core::v8::PinScope<'a, 'i>, + ) -> Result<::deno_core::v8::Local<'a, ::deno_core::v8::Value>, Self::Error> { + match self { + Self::First => { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "kind".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __values = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "FIRST".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + Ok( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __values, + ) + .into(), + ) + } + Self::Second { 0: __0 } => { + let __body = { + Ok::< + _, + ::deno_error::JsErrorBox, + >( + ::deno_core::convert::ToV8::to_v8(__0, __scope) + .map_err(::deno_error::JsErrorBox::from_err)?, + ) + }?; + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "kind".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ::deno_core::v8::String::new_from_one_byte( + __scope, + "data".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "second".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + __body, + ]; + Ok( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + } + } + } +} + +impl<'a> ::deno_core::convert::ToV8<'a> for SerdeEnum { + type Error = ::deno_error::JsErrorBox; + fn to_v8<'i>( + self, + __scope: &mut ::deno_core::v8::PinScope<'a, 'i>, + ) -> Result<::deno_core::v8::Local<'a, ::deno_core::v8::Value>, Self::Error> { + match self { + Self::WithSerde { data, other } => { + let __body = { + let __obj = ::deno_core::v8::Object::new(__scope); + let __key = ::deno_core::v8::String::new(__scope, stringify!(data)) + .unwrap() + .into(); + let __val = ::deno_core::serde_v8::to_v8(__scope, data) + .map_err(::deno_error::JsErrorBox::from_err)?; + __obj.set(__scope, __key, __val); + let __key = ::deno_core::v8::String::new( + __scope, + stringify!(renamedField), + ) + .unwrap() + .into(); + let __val = ::deno_core::serde_v8::to_v8(__scope, other) + .map_err(::deno_error::JsErrorBox::from_err)?; + __obj.set(__scope, __key, __val); + Ok::<_, ::deno_error::JsErrorBox>(__obj.into()) + }?; + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "withSerde".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[__body]; + Ok( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + } + Self::WithoutSerde { value } => { + let __body = { + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "value".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[ + ::deno_core::convert::ToV8::to_v8(value, __scope) + .map_err(::deno_error::JsErrorBox::from_err)?, + ]; + Ok::< + _, + ::deno_error::JsErrorBox, + >( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + }?; + let __null = ::deno_core::v8::null(__scope).into(); + let __keys = &[ + ::deno_core::v8::String::new_from_one_byte( + __scope, + "withoutSerde".as_bytes(), + ::deno_core::v8::NewStringType::Internalized, + ) + .unwrap() + .into(), + ]; + let __converters = &[__body]; + Ok( + ::deno_core::v8::Object::with_prototype_and_properties( + __scope, + __null, + __keys, + __converters, + ) + .into(), + ) + } + } + } +} diff --git a/ops/conversion/to_v8/test_cases/enum.rs b/ops/conversion/to_v8/test_cases/enum.rs new file mode 100644 index 000000000..ce494dfd3 --- /dev/null +++ b/ops/conversion/to_v8/test_cases/enum.rs @@ -0,0 +1,49 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +#![deny(warnings)] +deno_ops_compile_test_runner::prelude!(); + +#[derive(ToV8)] +pub enum SimpleEnum { + VariantA, + #[to_v8(rename = "customName")] + VariantB, + VariantC { + field: u32, + }, + #[to_v8(rename = "renamedWithFields")] + VariantD { + value: String, + }, +} + +#[derive(ToV8)] +#[to_v8(tag = "type")] +pub enum InternallyTaggedEnum { + #[v8(rename = "custom_a")] + A, + B { + data: u32, + }, +} + +#[derive(ToV8)] +#[to_v8(tag = "kind", content = "data")] +pub enum AdjacentlyTaggedEnum { + #[to_v8(rename = "FIRST")] + First, + Second(u32), +} + +#[derive(ToV8)] +pub enum SerdeEnum { + #[to_v8(serde)] + WithSerde { + data: Vec, + #[to_v8(rename = "renamedField")] + other: String, + }, + WithoutSerde { + value: u32, + }, +} diff --git a/ops/conversion/to_v8/test_cases/struct.out b/ops/conversion/to_v8/test_cases/struct.out index 7eaa58f81..84dd4dd03 100644 --- a/ops/conversion/to_v8/test_cases/struct.out +++ b/ops/conversion/to_v8/test_cases/struct.out @@ -4,6 +4,7 @@ impl<'a> ::deno_core::convert::ToV8<'a> for Struct { self, __scope: &mut ::deno_core::v8::PinScope<'a, 'i>, ) -> Result<::deno_core::v8::Local<'a, ::deno_core::v8::Value>, Self::Error> { + let Self { a, c, d, f } = self; let __null = ::deno_core::v8::null(__scope).into(); let __keys = &[ ::deno_core::v8::String::new_from_one_byte( @@ -36,16 +37,19 @@ impl<'a> ::deno_core::convert::ToV8<'a> for Struct { .into(), ]; let __converters = &[ - ::deno_core::convert::ToV8::to_v8(self.a, __scope) + ::deno_core::convert::ToV8::to_v8(a, __scope) .map_err(::deno_error::JsErrorBox::from_err)?, - ::deno_core::serde_v8::to_v8(__scope, self.c) + ::deno_core::serde_v8::to_v8(__scope, c) .map_err(::deno_error::JsErrorBox::from_err)?, - ::deno_core::convert::ToV8::to_v8(self.d, __scope) + ::deno_core::convert::ToV8::to_v8(d, __scope) .map_err(::deno_error::JsErrorBox::from_err)?, - ::deno_core::convert::ToV8::to_v8(self.f, __scope) + ::deno_core::convert::ToV8::to_v8(f, __scope) .map_err(::deno_error::JsErrorBox::from_err)?, ]; - Ok( + Ok::< + _, + ::deno_error::JsErrorBox, + >( ::deno_core::v8::Object::with_prototype_and_properties( __scope, __null, diff --git a/ops/conversion/to_v8/test_cases/struct_tuple.out b/ops/conversion/to_v8/test_cases/struct_tuple.out index 90e79f242..8c7b73e56 100644 --- a/ops/conversion/to_v8/test_cases/struct_tuple.out +++ b/ops/conversion/to_v8/test_cases/struct_tuple.out @@ -4,12 +4,16 @@ impl<'a> ::deno_core::convert::ToV8<'a> for Tuple { self, __scope: &mut ::deno_core::v8::PinScope<'a, 'i>, ) -> Result<::deno_core::v8::Local<'a, ::deno_core::v8::Value>, Self::Error> { + let Self { 0: __0, 1: __1 } = self; let __value = &[ - ::deno_core::serde_v8::to_v8(__scope, self.0) + ::deno_core::serde_v8::to_v8(__scope, __0) .map_err(::deno_error::JsErrorBox::from_err)?, - ::deno_core::convert::ToV8::to_v8(self.1, __scope) + ::deno_core::convert::ToV8::to_v8(__1, __scope) .map_err(::deno_error::JsErrorBox::from_err)?, ]; - Ok(::deno_core::v8::Array::new_with_elements(__scope, __value).into()) + Ok::< + _, + ::deno_error::JsErrorBox, + >(::deno_core::v8::Array::new_with_elements(__scope, __value).into()) } }