From 958639b06bdb39b04d1cd0947150eae6f5725615 Mon Sep 17 00:00:00 2001 From: eneoli Date: Thu, 1 Aug 2024 02:16:07 +0200 Subject: [PATCH 01/12] added vector support --- README.md | 2 + tsify-next-macros/src/attrs.rs | 22 ++++++ tsify-next-macros/src/wasm_bindgen.rs | 107 +++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 13f4db2..e220fa2 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ Tsify container attributes - `into_wasm_abi` implements `IntoWasmAbi` and `OptionIntoWasmAbi`. This can be converted directly from Rust to JS via `serde_json` or `serde-wasm-bindgen`. - `from_wasm_abi` implements `FromWasmAbi` and `OptionFromWasmAbi`. This is the opposite operation of the above. - `namespace` generates a namespace for the enum variants. +- `vector_into_wasm_abi` implements `VectorIntoWasmAbi`. This is the vector version of `into_wasm_abi` and is needed when working with vectors. +- `vector_from_wasm_abi` implements `VectorFromWasmAbi`. This is the opposite operation of the above. Tsify field attributes diff --git a/tsify-next-macros/src/attrs.rs b/tsify-next-macros/src/attrs.rs index 4ef01c3..2cf8570 100644 --- a/tsify-next-macros/src/attrs.rs +++ b/tsify-next-macros/src/attrs.rs @@ -8,8 +8,12 @@ use crate::comments::extract_doc_comments; pub struct TsifyContainerAttrs { /// Implement `IntoWasmAbi` for the type. pub into_wasm_abi: bool, + /// Implement `VectorIntoWasmAbi` for the type. + pub vector_into_wasm_abi: bool, /// Implement `FromWasmAbi` for the type. pub from_wasm_abi: bool, + /// Implement `VectorFromWasmAbi` for the type. + pub vector_from_wasm_abi: bool, /// Whether the type should be wrapped in a Typescript namespace. pub namespace: bool, /// Information about how the type should be serialized. @@ -46,7 +50,9 @@ impl TsifyContainerAttrs { pub fn from_derive_input(input: &syn::DeriveInput) -> syn::Result { let mut attrs = Self { into_wasm_abi: false, + vector_into_wasm_abi: false, from_wasm_abi: false, + vector_from_wasm_abi: false, namespace: false, ty_config: TypeGenerationConfig::default(), comments: extract_doc_comments(&input.attrs), @@ -66,6 +72,14 @@ impl TsifyContainerAttrs { return Ok(()); } + if meta.path.is_ident("vector_into_wasm_abi") { + if attrs.vector_into_wasm_abi { + return Err(meta.error("duplicate attribute")); + } + attrs.vector_into_wasm_abi = true; + return Ok(()); + } + if meta.path.is_ident("from_wasm_abi") { if attrs.from_wasm_abi { return Err(meta.error("duplicate attribute")); @@ -73,6 +87,14 @@ impl TsifyContainerAttrs { attrs.from_wasm_abi = true; return Ok(()); } + + if meta.path.is_ident("vector_from_wasm_abi") { + if attrs.vector_from_wasm_abi { + return Err(meta.error("duplicate attribute")); + } + attrs.vector_from_wasm_abi = true; + return Ok(()); + } if meta.path.is_ident("namespace") { if !matches!(input.data, syn::Data::Enum(_)) { diff --git a/tsify-next-macros/src/wasm_bindgen.rs b/tsify-next-macros/src/wasm_bindgen.rs index e0dc094..8806172 100644 --- a/tsify-next-macros/src/wasm_bindgen.rs +++ b/tsify-next-macros/src/wasm_bindgen.rs @@ -29,6 +29,18 @@ pub fn expand(cont: &Container, decl: Decl) -> TokenStream { } }); + let wasm_vector_abi = attrs.vector_into_wasm_abi || attrs.vector_from_wasm_abi; + let wasm_describe_vector = wasm_vector_abi.then(|| { + quote! { + impl #impl_generics WasmDescribeVector for #ident #ty_generics #where_clause { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } + } + }); + let use_serde = wasm_abi.then(|| match cont.serde_container.attrs.custom_serde_path() { Some(path) => quote! { use #path as _serde; @@ -37,9 +49,19 @@ pub fn expand(cont: &Container, decl: Decl) -> TokenStream { extern crate serde as _serde; }, }); + let into_wasm_abi = attrs.into_wasm_abi.then(|| expand_into_wasm_abi(cont)); + + let vector_into_wasm_abi = attrs + .vector_into_wasm_abi + .then(|| expand_vector_into_wasm_abi(cont)); + let from_wasm_abi = attrs.from_wasm_abi.then(|| expand_from_wasm_abi(cont)); + let vector_from_wasm_abi = attrs + .vector_from_wasm_abi + .then(|| expand_vector_from_wasm_abi(cont)); + let typescript_type = decl.id(); let missing_as_null = attrs.ty_config.missing_as_null; @@ -52,8 +74,8 @@ pub fn expand(cont: &Container, decl: Decl) -> TokenStream { #use_serde use tsify_next::Tsify; use wasm_bindgen::{ - convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi, RefFromWasmAbi}, - describe::WasmDescribe, + convert::{FromWasmAbi, VectorFromWasmAbi, IntoWasmAbi, VectorIntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi, RefFromWasmAbi}, + describe::WasmDescribe, describe::WasmDescribeVector, prelude::*, }; @@ -76,8 +98,11 @@ pub fn expand(cont: &Container, decl: Decl) -> TokenStream { #typescript_custom_section #wasm_describe + #wasm_describe_vector #into_wasm_abi + #vector_into_wasm_abi #from_wasm_abi + #vector_from_wasm_abi }; } } @@ -147,6 +172,50 @@ fn expand_into_wasm_abi(cont: &Container) -> TokenStream { } } +fn expand_vector_into_wasm_abi(cont: &Container) -> TokenStream { + let ident = cont.ident(); + let serde_path = cont.serde_container.attrs.serde_path(); + + let borrowed_generics = cont.generics(); + let mut generics = cont.generics().clone(); + generics + .make_where_clause() + .predicates + .push(parse_quote!(#ident #borrowed_generics: #serde_path::Serialize)); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics VectorIntoWasmAbi for #ident #ty_generics #where_clause { + type Abi = ::Abi; + + #[inline] + fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi { + let values = vector + .iter() + .map(|value| + // wasm_bindgen doesn't forward the error message from the `into_js` result. + // https://github.com/rustwasm/wasm-bindgen/issues/2732 + // Until that issue is fixed, we don't directly use `unwrap_throw()` and instead build our + // own error message. + // Convert to `value.into_js().unwrap_throw().into()` when fixed. + match value.into_js() { + Ok(js) => js.into(), + Err(err) => { + let loc = core::panic::Location::caller(); + let msg = format!("(Converting type failed) {} ({}:{}:{})", err, loc.file(), loc.line(), loc.column()); + // In theory, `wasm_bindgen::throw_str(&msg)` should work, but the error emitted by `wasm_bindgen::throw_str` cannot be picked up by `#[should_panic(expect = ...)]` in tests, so we use a regular panic. + panic!("{}", msg); + } + }) + .collect(); + + JsValue::vector_into_abi(values) + } + } + } +} + fn expand_from_wasm_abi(cont: &Container) -> TokenStream { let ident = cont.ident(); let serde_path = cont.serde_container.attrs.serde_path(); @@ -206,3 +275,37 @@ fn expand_from_wasm_abi(cont: &Container) -> TokenStream { } } } + +fn expand_vector_from_wasm_abi(cont: &Container) -> TokenStream { + let ident = cont.ident(); + let serde_path = cont.serde_container.attrs.serde_path(); + + let mut generics = cont.generics().clone(); + + generics + .make_where_clause() + .predicates + .push(parse_quote!(Self: #serde_path::de::DeserializeOwned)); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics VectorFromWasmAbi for #ident #ty_generics #where_clause { + type Abi = ::Abi; + + #[inline] + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> { + JsValue::vector_from_abi(js) + .into_iter() + .map(|value| { + let result = Self::from_js(value); + if let Err(err) = result { + wasm_bindgen::throw_str(err.to_string().as_ref()); + } + result.unwrap_throw() + }) + .collect() + } + } + } +} From 19e94cd74ef0cb6dcd217e41dae57629b535e092 Mon Sep 17 00:00:00 2001 From: eneoli Date: Thu, 1 Aug 2024 14:19:55 +0200 Subject: [PATCH 02/12] fix formatting --- tsify-next-macros/src/attrs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsify-next-macros/src/attrs.rs b/tsify-next-macros/src/attrs.rs index 2cf8570..b802f11 100644 --- a/tsify-next-macros/src/attrs.rs +++ b/tsify-next-macros/src/attrs.rs @@ -87,7 +87,7 @@ impl TsifyContainerAttrs { attrs.from_wasm_abi = true; return Ok(()); } - + if meta.path.is_ident("vector_from_wasm_abi") { if attrs.vector_from_wasm_abi { return Err(meta.error("duplicate attribute")); From 3b5c434e953dac83dd1021d2996dee13a66ce32d Mon Sep 17 00:00:00 2001 From: eneoli Date: Thu, 1 Aug 2024 14:24:26 +0200 Subject: [PATCH 03/12] made clippy happy --- tsify-next-macros/src/parser.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tsify-next-macros/src/parser.rs b/tsify-next-macros/src/parser.rs index 67c5933..c10d490 100644 --- a/tsify-next-macros/src/parser.rs +++ b/tsify-next-macros/src/parser.rs @@ -255,7 +255,11 @@ impl<'a> Parser<'a> { .map(|variant| { let decl = self.create_type_alias_decl(self.parse_variant(variant)); if let Decl::TsTypeAlias(mut type_alias) = decl { - type_alias.id = variant.attrs.name().serialize_name().to_owned(); + variant + .attrs + .name() + .serialize_name() + .clone_into(&mut type_alias.id); type_alias.comments = extract_doc_comments(&variant.original.attrs); type_alias From d80decf024f6905be73f5ee1ffc91e513fb58cf1 Mon Sep 17 00:00:00 2001 From: eneoli Date: Thu, 1 Aug 2024 14:48:58 +0200 Subject: [PATCH 04/12] regenerated expanded tests code --- tests/expand/borrow.expanded.rs | 165 +----------- tests/expand/generic_enum.expanded.rs | 170 +----------- tests/expand/generic_struct.expanded.rs | 345 ++---------------------- tests/expand/type_alias.expanded.rs | 2 + 4 files changed, 38 insertions(+), 644 deletions(-) diff --git a/tests/expand/borrow.expanded.rs b/tests/expand/borrow.expanded.rs index 14dd9b5..2e58222 100644 --- a/tests/expand/borrow.expanded.rs +++ b/tests/expand/borrow.expanded.rs @@ -11,164 +11,15 @@ const _: () = { use tsify_next::Tsify; use wasm_bindgen::{ convert::{ - FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi, - RefFromWasmAbi, + FromWasmAbi, VectorFromWasmAbi, IntoWasmAbi, VectorIntoWasmAbi, + OptionFromWasmAbi, OptionIntoWasmAbi, RefFromWasmAbi, }, - describe::WasmDescribe, prelude::*, + describe::WasmDescribe, describe::WasmDescribeVector, prelude::*, }; - #[automatically_derived] - /// - #[repr(transparent)] - pub struct JsType { - obj: wasm_bindgen::JsValue, - } - #[automatically_derived] - const _: () = { - use wasm_bindgen::convert::TryFromJsValue; - use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi}; - use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; - use wasm_bindgen::convert::{RefFromWasmAbi, LongRefFromWasmAbi}; - use wasm_bindgen::describe::WasmDescribe; - use wasm_bindgen::{JsValue, JsCast, JsObject}; - use wasm_bindgen::__rt::core; - impl WasmDescribe for JsType { - fn describe() { - use wasm_bindgen::describe::*; - inform(NAMED_EXTERNREF); - inform(6u32); - inform(66u32); - inform(111u32); - inform(114u32); - inform(114u32); - inform(111u32); - inform(119u32); - } - } - impl IntoWasmAbi for JsType { - type Abi = ::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - self.obj.into_abi() - } - } - impl OptionIntoWasmAbi for JsType { - #[inline] - fn none() -> Self::Abi { - 0 - } - } - impl<'a> OptionIntoWasmAbi for &'a JsType { - #[inline] - fn none() -> Self::Abi { - 0 - } - } - impl FromWasmAbi for JsType { - type Abi = ::Abi; - #[inline] - unsafe fn from_abi(js: Self::Abi) -> Self { - JsType { - obj: JsValue::from_abi(js).into(), - } - } - } - impl OptionFromWasmAbi for JsType { - #[inline] - fn is_none(abi: &Self::Abi) -> bool { - *abi == 0 - } - } - impl<'a> IntoWasmAbi for &'a JsType { - type Abi = <&'a JsValue as IntoWasmAbi>::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - (&self.obj).into_abi() - } - } - impl RefFromWasmAbi for JsType { - type Abi = ::Abi; - type Anchor = core::mem::ManuallyDrop; - #[inline] - unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { - let tmp = ::ref_from_abi(js); - core::mem::ManuallyDrop::new(JsType { - obj: core::mem::ManuallyDrop::into_inner(tmp).into(), - }) - } - } - impl LongRefFromWasmAbi for JsType { - type Abi = ::Abi; - type Anchor = JsType; - #[inline] - unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor { - let tmp = ::long_ref_from_abi(js); - JsType { obj: tmp.into() } - } - } - impl From for JsType { - #[inline] - fn from(obj: JsValue) -> JsType { - JsType { obj: obj.into() } - } - } - impl AsRef for JsType { - #[inline] - fn as_ref(&self) -> &JsValue { - self.obj.as_ref() - } - } - impl AsRef for JsType { - #[inline] - fn as_ref(&self) -> &JsType { - self - } - } - impl From for JsValue { - #[inline] - fn from(obj: JsType) -> JsValue { - obj.obj.into() - } - } - impl JsCast for JsType { - fn instanceof(val: &JsValue) -> bool { - #[cfg( - not( - all( - target_arch = "wasm32", - not(any(target_os = "emscripten", target_os = "wasi")) - ) - ) - )] - unsafe fn __wbg_instanceof_JsType_1641ac20ec916ae7(_: u32) -> u32 { - { - ::std::rt::begin_panic( - "cannot check instanceof on non-wasm targets", - ); - }; - } - unsafe { - let idx = val.into_abi(); - __wbg_instanceof_JsType_1641ac20ec916ae7(idx) != 0 - } - } - #[inline] - fn unchecked_from_js(val: JsValue) -> Self { - JsType { obj: val.into() } - } - #[inline] - fn unchecked_from_js_ref(val: &JsValue) -> &Self { - unsafe { &*(val as *const JsValue as *const JsType) } - } - } - impl JsObject for JsType {} - }; - #[automatically_derived] - impl core::ops::Deref for JsType { - type Target = wasm_bindgen::JsValue; - #[inline] - fn deref(&self) -> &wasm_bindgen::JsValue { - &self.obj - } + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(typescript_type = "Borrow")] + pub type JsType; } impl<'a> Tsify for Borrow<'a> { type JsType = JsType; @@ -179,6 +30,8 @@ const _: () = { large_number_types_as_bigints: false, }; } + #[wasm_bindgen(typescript_custom_section)] + const TS_APPEND_CONTENT: &'static str = "export interface Borrow {\n raw: string;\n cow: string;\n}"; impl<'a> WasmDescribe for Borrow<'a> { #[inline] fn describe() { diff --git a/tests/expand/generic_enum.expanded.rs b/tests/expand/generic_enum.expanded.rs index 97730a5..691d068 100644 --- a/tests/expand/generic_enum.expanded.rs +++ b/tests/expand/generic_enum.expanded.rs @@ -12,169 +12,15 @@ const _: () = { use tsify_next::Tsify; use wasm_bindgen::{ convert::{ - FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi, - RefFromWasmAbi, + FromWasmAbi, VectorFromWasmAbi, IntoWasmAbi, VectorIntoWasmAbi, + OptionFromWasmAbi, OptionIntoWasmAbi, RefFromWasmAbi, }, - describe::WasmDescribe, prelude::*, + describe::WasmDescribe, describe::WasmDescribeVector, prelude::*, }; - #[automatically_derived] - /// - #[repr(transparent)] - pub struct JsType { - obj: wasm_bindgen::JsValue, - } - #[automatically_derived] - const _: () = { - use wasm_bindgen::convert::TryFromJsValue; - use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi}; - use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; - use wasm_bindgen::convert::{RefFromWasmAbi, LongRefFromWasmAbi}; - use wasm_bindgen::describe::WasmDescribe; - use wasm_bindgen::{JsValue, JsCast, JsObject}; - use wasm_bindgen::__rt::core; - impl WasmDescribe for JsType { - fn describe() { - use wasm_bindgen::describe::*; - inform(NAMED_EXTERNREF); - inform(11u32); - inform(71u32); - inform(101u32); - inform(110u32); - inform(101u32); - inform(114u32); - inform(105u32); - inform(99u32); - inform(69u32); - inform(110u32); - inform(117u32); - inform(109u32); - } - } - impl IntoWasmAbi for JsType { - type Abi = ::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - self.obj.into_abi() - } - } - impl OptionIntoWasmAbi for JsType { - #[inline] - fn none() -> Self::Abi { - 0 - } - } - impl<'a> OptionIntoWasmAbi for &'a JsType { - #[inline] - fn none() -> Self::Abi { - 0 - } - } - impl FromWasmAbi for JsType { - type Abi = ::Abi; - #[inline] - unsafe fn from_abi(js: Self::Abi) -> Self { - JsType { - obj: JsValue::from_abi(js).into(), - } - } - } - impl OptionFromWasmAbi for JsType { - #[inline] - fn is_none(abi: &Self::Abi) -> bool { - *abi == 0 - } - } - impl<'a> IntoWasmAbi for &'a JsType { - type Abi = <&'a JsValue as IntoWasmAbi>::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - (&self.obj).into_abi() - } - } - impl RefFromWasmAbi for JsType { - type Abi = ::Abi; - type Anchor = core::mem::ManuallyDrop; - #[inline] - unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { - let tmp = ::ref_from_abi(js); - core::mem::ManuallyDrop::new(JsType { - obj: core::mem::ManuallyDrop::into_inner(tmp).into(), - }) - } - } - impl LongRefFromWasmAbi for JsType { - type Abi = ::Abi; - type Anchor = JsType; - #[inline] - unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor { - let tmp = ::long_ref_from_abi(js); - JsType { obj: tmp.into() } - } - } - impl From for JsType { - #[inline] - fn from(obj: JsValue) -> JsType { - JsType { obj: obj.into() } - } - } - impl AsRef for JsType { - #[inline] - fn as_ref(&self) -> &JsValue { - self.obj.as_ref() - } - } - impl AsRef for JsType { - #[inline] - fn as_ref(&self) -> &JsType { - self - } - } - impl From for JsValue { - #[inline] - fn from(obj: JsType) -> JsValue { - obj.obj.into() - } - } - impl JsCast for JsType { - fn instanceof(val: &JsValue) -> bool { - #[cfg( - not( - all( - target_arch = "wasm32", - not(any(target_os = "emscripten", target_os = "wasi")) - ) - ) - )] - unsafe fn __wbg_instanceof_JsType_1641ac20ec916ae7(_: u32) -> u32 { - { - ::std::rt::begin_panic( - "cannot check instanceof on non-wasm targets", - ); - }; - } - unsafe { - let idx = val.into_abi(); - __wbg_instanceof_JsType_1641ac20ec916ae7(idx) != 0 - } - } - #[inline] - fn unchecked_from_js(val: JsValue) -> Self { - JsType { obj: val.into() } - } - #[inline] - fn unchecked_from_js_ref(val: &JsValue) -> &Self { - unsafe { &*(val as *const JsValue as *const JsType) } - } - } - impl JsObject for JsType {} - }; - #[automatically_derived] - impl core::ops::Deref for JsType { - type Target = wasm_bindgen::JsValue; - #[inline] - fn deref(&self) -> &wasm_bindgen::JsValue { - &self.obj - } + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(typescript_type = "GenericEnum")] + pub type JsType; } impl Tsify for GenericEnum { type JsType = JsType; @@ -185,6 +31,8 @@ const _: () = { large_number_types_as_bigints: false, }; } + #[wasm_bindgen(typescript_custom_section)] + const TS_APPEND_CONTENT: &'static str = "export type GenericEnum = \"Unit\" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };"; impl WasmDescribe for GenericEnum { #[inline] fn describe() { diff --git a/tests/expand/generic_struct.expanded.rs b/tests/expand/generic_struct.expanded.rs index d8e5957..2effda2 100644 --- a/tests/expand/generic_struct.expanded.rs +++ b/tests/expand/generic_struct.expanded.rs @@ -9,171 +9,15 @@ const _: () = { use tsify_next::Tsify; use wasm_bindgen::{ convert::{ - FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi, - RefFromWasmAbi, + FromWasmAbi, VectorFromWasmAbi, IntoWasmAbi, VectorIntoWasmAbi, + OptionFromWasmAbi, OptionIntoWasmAbi, RefFromWasmAbi, }, - describe::WasmDescribe, prelude::*, + describe::WasmDescribe, describe::WasmDescribeVector, prelude::*, }; - #[automatically_derived] - /// - #[repr(transparent)] - pub struct JsType { - obj: wasm_bindgen::JsValue, - } - #[automatically_derived] - const _: () = { - use wasm_bindgen::convert::TryFromJsValue; - use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi}; - use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; - use wasm_bindgen::convert::{RefFromWasmAbi, LongRefFromWasmAbi}; - use wasm_bindgen::describe::WasmDescribe; - use wasm_bindgen::{JsValue, JsCast, JsObject}; - use wasm_bindgen::__rt::core; - impl WasmDescribe for JsType { - fn describe() { - use wasm_bindgen::describe::*; - inform(NAMED_EXTERNREF); - inform(13u32); - inform(71u32); - inform(101u32); - inform(110u32); - inform(101u32); - inform(114u32); - inform(105u32); - inform(99u32); - inform(83u32); - inform(116u32); - inform(114u32); - inform(117u32); - inform(99u32); - inform(116u32); - } - } - impl IntoWasmAbi for JsType { - type Abi = ::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - self.obj.into_abi() - } - } - impl OptionIntoWasmAbi for JsType { - #[inline] - fn none() -> Self::Abi { - 0 - } - } - impl<'a> OptionIntoWasmAbi for &'a JsType { - #[inline] - fn none() -> Self::Abi { - 0 - } - } - impl FromWasmAbi for JsType { - type Abi = ::Abi; - #[inline] - unsafe fn from_abi(js: Self::Abi) -> Self { - JsType { - obj: JsValue::from_abi(js).into(), - } - } - } - impl OptionFromWasmAbi for JsType { - #[inline] - fn is_none(abi: &Self::Abi) -> bool { - *abi == 0 - } - } - impl<'a> IntoWasmAbi for &'a JsType { - type Abi = <&'a JsValue as IntoWasmAbi>::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - (&self.obj).into_abi() - } - } - impl RefFromWasmAbi for JsType { - type Abi = ::Abi; - type Anchor = core::mem::ManuallyDrop; - #[inline] - unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { - let tmp = ::ref_from_abi(js); - core::mem::ManuallyDrop::new(JsType { - obj: core::mem::ManuallyDrop::into_inner(tmp).into(), - }) - } - } - impl LongRefFromWasmAbi for JsType { - type Abi = ::Abi; - type Anchor = JsType; - #[inline] - unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor { - let tmp = ::long_ref_from_abi(js); - JsType { obj: tmp.into() } - } - } - impl From for JsType { - #[inline] - fn from(obj: JsValue) -> JsType { - JsType { obj: obj.into() } - } - } - impl AsRef for JsType { - #[inline] - fn as_ref(&self) -> &JsValue { - self.obj.as_ref() - } - } - impl AsRef for JsType { - #[inline] - fn as_ref(&self) -> &JsType { - self - } - } - impl From for JsValue { - #[inline] - fn from(obj: JsType) -> JsValue { - obj.obj.into() - } - } - impl JsCast for JsType { - fn instanceof(val: &JsValue) -> bool { - #[cfg( - not( - all( - target_arch = "wasm32", - not(any(target_os = "emscripten", target_os = "wasi")) - ) - ) - )] - unsafe fn __wbg_instanceof_JsType_1641ac20ec916ae7(_: u32) -> u32 { - { - ::std::rt::begin_panic( - "cannot check instanceof on non-wasm targets", - ); - }; - } - unsafe { - let idx = val.into_abi(); - __wbg_instanceof_JsType_1641ac20ec916ae7(idx) != 0 - } - } - #[inline] - fn unchecked_from_js(val: JsValue) -> Self { - JsType { obj: val.into() } - } - #[inline] - fn unchecked_from_js_ref(val: &JsValue) -> &Self { - unsafe { &*(val as *const JsValue as *const JsType) } - } - } - impl JsObject for JsType {} - }; - #[automatically_derived] - impl core::ops::Deref for JsType { - type Target = wasm_bindgen::JsValue; - #[inline] - fn deref(&self) -> &wasm_bindgen::JsValue { - &self.obj - } + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(typescript_type = "GenericStruct")] + pub type JsType; } impl Tsify for GenericStruct { type JsType = JsType; @@ -184,6 +28,8 @@ const _: () = { large_number_types_as_bigints: false, }; } + #[wasm_bindgen(typescript_custom_section)] + const TS_APPEND_CONTENT: &'static str = "export interface GenericStruct {\n x: T;\n}"; impl WasmDescribe for GenericStruct { #[inline] fn describe() { @@ -325,172 +171,15 @@ const _: () = { use tsify_next::Tsify; use wasm_bindgen::{ convert::{ - FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi, OptionIntoWasmAbi, - RefFromWasmAbi, + FromWasmAbi, VectorFromWasmAbi, IntoWasmAbi, VectorIntoWasmAbi, + OptionFromWasmAbi, OptionIntoWasmAbi, RefFromWasmAbi, }, - describe::WasmDescribe, prelude::*, + describe::WasmDescribe, describe::WasmDescribeVector, prelude::*, }; - #[automatically_derived] - /// - #[repr(transparent)] - pub struct JsType { - obj: wasm_bindgen::JsValue, - } - #[automatically_derived] - const _: () = { - use wasm_bindgen::convert::TryFromJsValue; - use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi}; - use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; - use wasm_bindgen::convert::{RefFromWasmAbi, LongRefFromWasmAbi}; - use wasm_bindgen::describe::WasmDescribe; - use wasm_bindgen::{JsValue, JsCast, JsObject}; - use wasm_bindgen::__rt::core; - impl WasmDescribe for JsType { - fn describe() { - use wasm_bindgen::describe::*; - inform(NAMED_EXTERNREF); - inform(14u32); - inform(71u32); - inform(101u32); - inform(110u32); - inform(101u32); - inform(114u32); - inform(105u32); - inform(99u32); - inform(78u32); - inform(101u32); - inform(119u32); - inform(116u32); - inform(121u32); - inform(112u32); - inform(101u32); - } - } - impl IntoWasmAbi for JsType { - type Abi = ::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - self.obj.into_abi() - } - } - impl OptionIntoWasmAbi for JsType { - #[inline] - fn none() -> Self::Abi { - 0 - } - } - impl<'a> OptionIntoWasmAbi for &'a JsType { - #[inline] - fn none() -> Self::Abi { - 0 - } - } - impl FromWasmAbi for JsType { - type Abi = ::Abi; - #[inline] - unsafe fn from_abi(js: Self::Abi) -> Self { - JsType { - obj: JsValue::from_abi(js).into(), - } - } - } - impl OptionFromWasmAbi for JsType { - #[inline] - fn is_none(abi: &Self::Abi) -> bool { - *abi == 0 - } - } - impl<'a> IntoWasmAbi for &'a JsType { - type Abi = <&'a JsValue as IntoWasmAbi>::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - (&self.obj).into_abi() - } - } - impl RefFromWasmAbi for JsType { - type Abi = ::Abi; - type Anchor = core::mem::ManuallyDrop; - #[inline] - unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { - let tmp = ::ref_from_abi(js); - core::mem::ManuallyDrop::new(JsType { - obj: core::mem::ManuallyDrop::into_inner(tmp).into(), - }) - } - } - impl LongRefFromWasmAbi for JsType { - type Abi = ::Abi; - type Anchor = JsType; - #[inline] - unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor { - let tmp = ::long_ref_from_abi(js); - JsType { obj: tmp.into() } - } - } - impl From for JsType { - #[inline] - fn from(obj: JsValue) -> JsType { - JsType { obj: obj.into() } - } - } - impl AsRef for JsType { - #[inline] - fn as_ref(&self) -> &JsValue { - self.obj.as_ref() - } - } - impl AsRef for JsType { - #[inline] - fn as_ref(&self) -> &JsType { - self - } - } - impl From for JsValue { - #[inline] - fn from(obj: JsType) -> JsValue { - obj.obj.into() - } - } - impl JsCast for JsType { - fn instanceof(val: &JsValue) -> bool { - #[cfg( - not( - all( - target_arch = "wasm32", - not(any(target_os = "emscripten", target_os = "wasi")) - ) - ) - )] - unsafe fn __wbg_instanceof_JsType_1641ac20ec916ae7(_: u32) -> u32 { - { - ::std::rt::begin_panic( - "cannot check instanceof on non-wasm targets", - ); - }; - } - unsafe { - let idx = val.into_abi(); - __wbg_instanceof_JsType_1641ac20ec916ae7(idx) != 0 - } - } - #[inline] - fn unchecked_from_js(val: JsValue) -> Self { - JsType { obj: val.into() } - } - #[inline] - fn unchecked_from_js_ref(val: &JsValue) -> &Self { - unsafe { &*(val as *const JsValue as *const JsType) } - } - } - impl JsObject for JsType {} - }; - #[automatically_derived] - impl core::ops::Deref for JsType { - type Target = wasm_bindgen::JsValue; - #[inline] - fn deref(&self) -> &wasm_bindgen::JsValue { - &self.obj - } + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(typescript_type = "GenericNewtype")] + pub type JsType; } impl Tsify for GenericNewtype { type JsType = JsType; @@ -501,6 +190,8 @@ const _: () = { large_number_types_as_bigints: false, }; } + #[wasm_bindgen(typescript_custom_section)] + const TS_APPEND_CONTENT: &'static str = "export type GenericNewtype = T;"; impl WasmDescribe for GenericNewtype { #[inline] fn describe() { diff --git a/tests/expand/type_alias.expanded.rs b/tests/expand/type_alias.expanded.rs index f6c0529..b14cf87 100644 --- a/tests/expand/type_alias.expanded.rs +++ b/tests/expand/type_alias.expanded.rs @@ -2,4 +2,6 @@ type TypeAlias = Foo; #[automatically_derived] const _: () = { use wasm_bindgen::prelude::*; + #[wasm_bindgen(typescript_custom_section)] + const TS_APPEND_CONTENT: &'static str = "export type TypeAlias = Foo;"; }; From 246d01a8f84984211367ce4a47b381934ba4f5e8 Mon Sep 17 00:00:00 2001 From: eneoli Date: Thu, 1 Aug 2024 14:49:21 +0200 Subject: [PATCH 05/12] removed duplicated dependencies from dev-dependencies --- Cargo.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a75cf4a..c822a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ categories = ["wasm"] [dependencies] tsify-next-macros = { path = "tsify-next-macros", version = "0.5.3" } wasm-bindgen = { version = "0.2.86", optional = true } -serde = { version = "1.0", optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0", optional = true } serde-wasm-bindgen = { version = "0.6", optional = true } gloo-utils = { version = "0.2", optional = true } @@ -26,10 +26,6 @@ indoc = "2.0.5" js-sys = "0.3" macrotest = "1.0" pretty_assertions = "1.4.0" -serde = { version = "1.0", features = ["derive"] } -serde-wasm-bindgen = "0.6" -serde_json = "1.0" -wasm-bindgen = "0.2" wasm-bindgen-test = "0.3" [features] From 728f7cb6ba1ecd109790a1577a5aa7b7ff4c4f99 Mon Sep 17 00:00:00 2001 From: eneoli Date: Thu, 1 Aug 2024 17:30:48 +0200 Subject: [PATCH 06/12] removed unused comments field --- tsify-next-macros/src/attrs.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/tsify-next-macros/src/attrs.rs b/tsify-next-macros/src/attrs.rs index b802f11..047613e 100644 --- a/tsify-next-macros/src/attrs.rs +++ b/tsify-next-macros/src/attrs.rs @@ -18,8 +18,6 @@ pub struct TsifyContainerAttrs { pub namespace: bool, /// Information about how the type should be serialized. pub ty_config: TypeGenerationConfig, - /// Comments associated with the type. These will be written out to the generated Typescript. - pub comments: Vec, } /// Configuration affecting how Typescript types are generated. @@ -55,7 +53,6 @@ impl TsifyContainerAttrs { vector_from_wasm_abi: false, namespace: false, ty_config: TypeGenerationConfig::default(), - comments: extract_doc_comments(&input.attrs), }; for attr in &input.attrs { From 290dbc295c1ce50d90ae26b9885896fafff343cc Mon Sep 17 00:00:00 2001 From: eneoli Date: Thu, 1 Aug 2024 17:48:08 +0200 Subject: [PATCH 07/12] removed dead code (TsifyFieldAttrs::comments field) --- tsify-next-macros/src/attrs.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tsify-next-macros/src/attrs.rs b/tsify-next-macros/src/attrs.rs index 047613e..1dea7ae 100644 --- a/tsify-next-macros/src/attrs.rs +++ b/tsify-next-macros/src/attrs.rs @@ -1,7 +1,5 @@ use serde_derive_internals::ast::Field; -use crate::comments::extract_doc_comments; - /// Attributes that can be applied to a type decorated with `#[derive(Tsify)]`. /// E.g., through `#[tsify(into_wasm_abi)]`. #[derive(Debug, Default)] @@ -173,7 +171,6 @@ impl TsifyContainerAttrs { pub struct TsifyFieldAttrs { pub type_override: Option, pub optional: bool, - pub comments: Vec, } impl TsifyFieldAttrs { @@ -181,7 +178,6 @@ impl TsifyFieldAttrs { let mut attrs = Self { type_override: None, optional: false, - comments: extract_doc_comments(&field.original.attrs), }; for attr in &field.original.attrs { From fdeca593a3ada22fcd2db63222549f5d7d071bd2 Mon Sep 17 00:00:00 2001 From: eneoli Date: Thu, 1 Aug 2024 23:58:25 +0200 Subject: [PATCH 08/12] removed extra flags for vector versions --- README.md | 2 -- tsify-next-macros/src/attrs.rs | 22 ------------ tsify-next-macros/src/wasm_bindgen.rs | 50 --------------------------- 3 files changed, 74 deletions(-) diff --git a/README.md b/README.md index e220fa2..13f4db2 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,6 @@ Tsify container attributes - `into_wasm_abi` implements `IntoWasmAbi` and `OptionIntoWasmAbi`. This can be converted directly from Rust to JS via `serde_json` or `serde-wasm-bindgen`. - `from_wasm_abi` implements `FromWasmAbi` and `OptionFromWasmAbi`. This is the opposite operation of the above. - `namespace` generates a namespace for the enum variants. -- `vector_into_wasm_abi` implements `VectorIntoWasmAbi`. This is the vector version of `into_wasm_abi` and is needed when working with vectors. -- `vector_from_wasm_abi` implements `VectorFromWasmAbi`. This is the opposite operation of the above. Tsify field attributes diff --git a/tsify-next-macros/src/attrs.rs b/tsify-next-macros/src/attrs.rs index 1dea7ae..a2ec69b 100644 --- a/tsify-next-macros/src/attrs.rs +++ b/tsify-next-macros/src/attrs.rs @@ -6,12 +6,8 @@ use serde_derive_internals::ast::Field; pub struct TsifyContainerAttrs { /// Implement `IntoWasmAbi` for the type. pub into_wasm_abi: bool, - /// Implement `VectorIntoWasmAbi` for the type. - pub vector_into_wasm_abi: bool, /// Implement `FromWasmAbi` for the type. pub from_wasm_abi: bool, - /// Implement `VectorFromWasmAbi` for the type. - pub vector_from_wasm_abi: bool, /// Whether the type should be wrapped in a Typescript namespace. pub namespace: bool, /// Information about how the type should be serialized. @@ -46,9 +42,7 @@ impl TsifyContainerAttrs { pub fn from_derive_input(input: &syn::DeriveInput) -> syn::Result { let mut attrs = Self { into_wasm_abi: false, - vector_into_wasm_abi: false, from_wasm_abi: false, - vector_from_wasm_abi: false, namespace: false, ty_config: TypeGenerationConfig::default(), }; @@ -67,14 +61,6 @@ impl TsifyContainerAttrs { return Ok(()); } - if meta.path.is_ident("vector_into_wasm_abi") { - if attrs.vector_into_wasm_abi { - return Err(meta.error("duplicate attribute")); - } - attrs.vector_into_wasm_abi = true; - return Ok(()); - } - if meta.path.is_ident("from_wasm_abi") { if attrs.from_wasm_abi { return Err(meta.error("duplicate attribute")); @@ -83,14 +69,6 @@ impl TsifyContainerAttrs { return Ok(()); } - if meta.path.is_ident("vector_from_wasm_abi") { - if attrs.vector_from_wasm_abi { - return Err(meta.error("duplicate attribute")); - } - attrs.vector_from_wasm_abi = true; - return Ok(()); - } - if meta.path.is_ident("namespace") { if !matches!(input.data, syn::Data::Enum(_)) { return Err(meta.error("#[tsify(namespace)] can only be used on enums")); diff --git a/tsify-next-macros/src/wasm_bindgen.rs b/tsify-next-macros/src/wasm_bindgen.rs index 8806172..d802c29 100644 --- a/tsify-next-macros/src/wasm_bindgen.rs +++ b/tsify-next-macros/src/wasm_bindgen.rs @@ -26,12 +26,7 @@ pub fn expand(cont: &Container, decl: Decl) -> TokenStream { ::JsType::describe() } } - } - }); - let wasm_vector_abi = attrs.vector_into_wasm_abi || attrs.vector_from_wasm_abi; - let wasm_describe_vector = wasm_vector_abi.then(|| { - quote! { impl #impl_generics WasmDescribeVector for #ident #ty_generics #where_clause { #[inline] fn describe_vector() { @@ -51,17 +46,8 @@ pub fn expand(cont: &Container, decl: Decl) -> TokenStream { }); let into_wasm_abi = attrs.into_wasm_abi.then(|| expand_into_wasm_abi(cont)); - - let vector_into_wasm_abi = attrs - .vector_into_wasm_abi - .then(|| expand_vector_into_wasm_abi(cont)); - let from_wasm_abi = attrs.from_wasm_abi.then(|| expand_from_wasm_abi(cont)); - let vector_from_wasm_abi = attrs - .vector_from_wasm_abi - .then(|| expand_vector_from_wasm_abi(cont)); - let typescript_type = decl.id(); let missing_as_null = attrs.ty_config.missing_as_null; @@ -98,11 +84,8 @@ pub fn expand(cont: &Container, decl: Decl) -> TokenStream { #typescript_custom_section #wasm_describe - #wasm_describe_vector #into_wasm_abi - #vector_into_wasm_abi #from_wasm_abi - #vector_from_wasm_abi }; } } @@ -169,23 +152,7 @@ fn expand_into_wasm_abi(cont: &Container) -> TokenStream { } } } - } -} - -fn expand_vector_into_wasm_abi(cont: &Container) -> TokenStream { - let ident = cont.ident(); - let serde_path = cont.serde_container.attrs.serde_path(); - - let borrowed_generics = cont.generics(); - let mut generics = cont.generics().clone(); - generics - .make_where_clause() - .predicates - .push(parse_quote!(#ident #borrowed_generics: #serde_path::Serialize)); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - quote! { impl #impl_generics VectorIntoWasmAbi for #ident #ty_generics #where_clause { type Abi = ::Abi; @@ -198,7 +165,6 @@ fn expand_vector_into_wasm_abi(cont: &Container) -> TokenStream { // https://github.com/rustwasm/wasm-bindgen/issues/2732 // Until that issue is fixed, we don't directly use `unwrap_throw()` and instead build our // own error message. - // Convert to `value.into_js().unwrap_throw().into()` when fixed. match value.into_js() { Ok(js) => js.into(), Err(err) => { @@ -273,23 +239,7 @@ fn expand_from_wasm_abi(cont: &Container) -> TokenStream { SelfOwner(result.unwrap_throw()) } } - } -} - -fn expand_vector_from_wasm_abi(cont: &Container) -> TokenStream { - let ident = cont.ident(); - let serde_path = cont.serde_container.attrs.serde_path(); - - let mut generics = cont.generics().clone(); - generics - .make_where_clause() - .predicates - .push(parse_quote!(Self: #serde_path::de::DeserializeOwned)); - - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - quote! { impl #impl_generics VectorFromWasmAbi for #ident #ty_generics #where_clause { type Abi = ::Abi; From 9ab4df0a7158d9ddea9e396f9a776d48529c0af3 Mon Sep 17 00:00:00 2001 From: eneoli Date: Fri, 2 Aug 2024 00:43:43 +0200 Subject: [PATCH 09/12] added tests --- tests-e2e/reference_output/test1/test1.d.ts | 12 ++ tests-e2e/test1/entry_point.rs | 17 +++ tests/expand/borrow.expanded.rs | 66 ++++++++++ tests/expand/generic_enum.expanded.rs | 66 ++++++++++ tests/expand/generic_struct.expanded.rs | 132 ++++++++++++++++++++ tests/wasm.rs | 63 +++++++++- 6 files changed, 350 insertions(+), 6 deletions(-) diff --git a/tests-e2e/reference_output/test1/test1.d.ts b/tests-e2e/reference_output/test1/test1.d.ts index 7ac75a5..c7675da 100644 --- a/tests-e2e/reference_output/test1/test1.d.ts +++ b/tests-e2e/reference_output/test1/test1.d.ts @@ -1,9 +1,21 @@ /* tslint:disable */ /* eslint-disable */ /** +* @param {Point} point +*/ +export function consume(point: Point): void; +/** * @returns {Point} */ export function into_js(): Point; +/** +* @param {(Point)[]} points +*/ +export function consume_vector(points: (Point)[]): void; +/** +* @returns {(Point)[]} +*/ +export function vector_into_js(): (Point)[]; export interface Point { x: number; y: number; diff --git a/tests-e2e/test1/entry_point.rs b/tests-e2e/test1/entry_point.rs index 8c10c71..18d4015 100644 --- a/tests-e2e/test1/entry_point.rs +++ b/tests-e2e/test1/entry_point.rs @@ -9,7 +9,24 @@ pub struct Point { y: i32, } +#[wasm_bindgen] +pub fn consume(point: Point) { +} + #[wasm_bindgen] pub fn into_js() -> Point { Point { x: 0, y: 0 } } + +#[wasm_bindgen] +pub fn consume_vector(points: Vec) { +} + +#[wasm_bindgen] +pub fn vector_into_js() -> Vec { + vec![ + Point { x: 1, y: 6 }, + Point { x: 2, y: 5 }, + Point { x: 3, y: 4 }, + ] +} \ No newline at end of file diff --git a/tests/expand/borrow.expanded.rs b/tests/expand/borrow.expanded.rs index 2e58222..410f218 100644 --- a/tests/expand/borrow.expanded.rs +++ b/tests/expand/borrow.expanded.rs @@ -38,6 +38,12 @@ const _: () = { ::JsType::describe() } } + impl<'a> WasmDescribeVector for Borrow<'a> { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } impl<'a> IntoWasmAbi for Borrow<'a> where Borrow<'a>: _serde::Serialize, @@ -120,6 +126,47 @@ const _: () = { } } } + impl<'a> VectorIntoWasmAbi for Borrow<'a> + where + Borrow<'a>: _serde::Serialize, + { + type Abi = ::Abi; + #[inline] + fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi { + let values = vector + .iter() + .map(|value| match value.into_js() { + Ok(js) => js.into(), + Err(err) => { + let loc = core::panic::Location::caller(); + let msg = { + let res = ::alloc::fmt::format( + format_args!( + "(Converting type failed) {0} ({1}:{2}:{3})", err, loc + .file(), loc.line(), loc.column(), + ), + ); + res + }; + { + #[cold] + #[track_caller] + #[inline(never)] + #[rustc_const_panic_str] + #[rustc_do_not_const_check] + const fn panic_cold_display( + arg: &T, + ) -> ! { + ::core::panicking::panic_display(arg) + } + panic_cold_display(&msg); + }; + } + }) + .collect(); + JsValue::vector_into_abi(values) + } + } impl<'a> FromWasmAbi for Borrow<'a> where Self: _serde::de::DeserializeOwned, @@ -164,4 +211,23 @@ const _: () = { SelfOwner(result.unwrap_throw()) } } + impl<'a> VectorFromWasmAbi for Borrow<'a> + where + Self: _serde::de::DeserializeOwned, + { + type Abi = ::Abi; + #[inline] + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> { + JsValue::vector_from_abi(js) + .into_iter() + .map(|value| { + let result = Self::from_js(value); + if let Err(err) = result { + wasm_bindgen::throw_str(err.to_string().as_ref()); + } + result.unwrap_throw() + }) + .collect() + } + } }; diff --git a/tests/expand/generic_enum.expanded.rs b/tests/expand/generic_enum.expanded.rs index 691d068..85f09e8 100644 --- a/tests/expand/generic_enum.expanded.rs +++ b/tests/expand/generic_enum.expanded.rs @@ -39,6 +39,12 @@ const _: () = { ::JsType::describe() } } + impl WasmDescribeVector for GenericEnum { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } impl IntoWasmAbi for GenericEnum where GenericEnum: _serde::Serialize, @@ -121,6 +127,47 @@ const _: () = { } } } + impl VectorIntoWasmAbi for GenericEnum + where + GenericEnum: _serde::Serialize, + { + type Abi = ::Abi; + #[inline] + fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi { + let values = vector + .iter() + .map(|value| match value.into_js() { + Ok(js) => js.into(), + Err(err) => { + let loc = core::panic::Location::caller(); + let msg = { + let res = ::alloc::fmt::format( + format_args!( + "(Converting type failed) {0} ({1}:{2}:{3})", err, loc + .file(), loc.line(), loc.column(), + ), + ); + res + }; + { + #[cold] + #[track_caller] + #[inline(never)] + #[rustc_const_panic_str] + #[rustc_do_not_const_check] + const fn panic_cold_display( + arg: &T, + ) -> ! { + ::core::panicking::panic_display(arg) + } + panic_cold_display(&msg); + }; + } + }) + .collect(); + JsValue::vector_into_abi(values) + } + } impl FromWasmAbi for GenericEnum where Self: _serde::de::DeserializeOwned, @@ -165,4 +212,23 @@ const _: () = { SelfOwner(result.unwrap_throw()) } } + impl VectorFromWasmAbi for GenericEnum + where + Self: _serde::de::DeserializeOwned, + { + type Abi = ::Abi; + #[inline] + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> { + JsValue::vector_from_abi(js) + .into_iter() + .map(|value| { + let result = Self::from_js(value); + if let Err(err) = result { + wasm_bindgen::throw_str(err.to_string().as_ref()); + } + result.unwrap_throw() + }) + .collect() + } + } }; diff --git a/tests/expand/generic_struct.expanded.rs b/tests/expand/generic_struct.expanded.rs index 2effda2..e0d3598 100644 --- a/tests/expand/generic_struct.expanded.rs +++ b/tests/expand/generic_struct.expanded.rs @@ -36,6 +36,12 @@ const _: () = { ::JsType::describe() } } + impl WasmDescribeVector for GenericStruct { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } impl IntoWasmAbi for GenericStruct where GenericStruct: _serde::Serialize, @@ -118,6 +124,47 @@ const _: () = { } } } + impl VectorIntoWasmAbi for GenericStruct + where + GenericStruct: _serde::Serialize, + { + type Abi = ::Abi; + #[inline] + fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi { + let values = vector + .iter() + .map(|value| match value.into_js() { + Ok(js) => js.into(), + Err(err) => { + let loc = core::panic::Location::caller(); + let msg = { + let res = ::alloc::fmt::format( + format_args!( + "(Converting type failed) {0} ({1}:{2}:{3})", err, loc + .file(), loc.line(), loc.column(), + ), + ); + res + }; + { + #[cold] + #[track_caller] + #[inline(never)] + #[rustc_const_panic_str] + #[rustc_do_not_const_check] + const fn panic_cold_display( + arg: &T, + ) -> ! { + ::core::panicking::panic_display(arg) + } + panic_cold_display(&msg); + }; + } + }) + .collect(); + JsValue::vector_into_abi(values) + } + } impl FromWasmAbi for GenericStruct where Self: _serde::de::DeserializeOwned, @@ -162,6 +209,25 @@ const _: () = { SelfOwner(result.unwrap_throw()) } } + impl VectorFromWasmAbi for GenericStruct + where + Self: _serde::de::DeserializeOwned, + { + type Abi = ::Abi; + #[inline] + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> { + JsValue::vector_from_abi(js) + .into_iter() + .map(|value| { + let result = Self::from_js(value); + if let Err(err) = result { + wasm_bindgen::throw_str(err.to_string().as_ref()); + } + result.unwrap_throw() + }) + .collect() + } + } }; #[tsify(into_wasm_abi, from_wasm_abi)] pub struct GenericNewtype(T); @@ -198,6 +264,12 @@ const _: () = { ::JsType::describe() } } + impl WasmDescribeVector for GenericNewtype { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } impl IntoWasmAbi for GenericNewtype where GenericNewtype: _serde::Serialize, @@ -280,6 +352,47 @@ const _: () = { } } } + impl VectorIntoWasmAbi for GenericNewtype + where + GenericNewtype: _serde::Serialize, + { + type Abi = ::Abi; + #[inline] + fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi { + let values = vector + .iter() + .map(|value| match value.into_js() { + Ok(js) => js.into(), + Err(err) => { + let loc = core::panic::Location::caller(); + let msg = { + let res = ::alloc::fmt::format( + format_args!( + "(Converting type failed) {0} ({1}:{2}:{3})", err, loc + .file(), loc.line(), loc.column(), + ), + ); + res + }; + { + #[cold] + #[track_caller] + #[inline(never)] + #[rustc_const_panic_str] + #[rustc_do_not_const_check] + const fn panic_cold_display( + arg: &T, + ) -> ! { + ::core::panicking::panic_display(arg) + } + panic_cold_display(&msg); + }; + } + }) + .collect(); + JsValue::vector_into_abi(values) + } + } impl FromWasmAbi for GenericNewtype where Self: _serde::de::DeserializeOwned, @@ -324,4 +437,23 @@ const _: () = { SelfOwner(result.unwrap_throw()) } } + impl VectorFromWasmAbi for GenericNewtype + where + Self: _serde::de::DeserializeOwned, + { + type Abi = ::Abi; + #[inline] + unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> { + JsValue::vector_from_abi(js) + .into_iter() + .map(|value| { + let result = Self::from_js(value); + if let Err(err) = result { + wasm_bindgen::throw_str(err.to_string().as_ref()); + } + result.unwrap_throw() + }) + .collect() + } + } }; diff --git a/tests/wasm.rs b/tests/wasm.rs index 87d74d7..401096e 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -37,9 +37,18 @@ function validate(value, validation) { validation(value); } +function validateArray(value, validation) { + validation(value); + + // Validate twice to make sure the value is not moved in any way to rust + validation(value); +} + function noop(value) {} -module.exports = { validate, noop }; +function noopArray(value) {} + +module.exports = { validate, validateArray, noop }; "#)] extern "C" { #[wasm_bindgen(catch, js_name = "validate")] @@ -54,24 +63,54 @@ extern "C" { validation: &dyn Fn(&SimpleData), ) -> Result<(), JsValue>; + #[wasm_bindgen(catch, js_name = "validateArray")] + pub fn validate_array( + value: Vec, + validation: &dyn Fn(Box<[SimpleData]>), + ) -> Result<(), JsValue>; + #[wasm_bindgen(catch, js_name = "noop")] pub fn do_not_serialize(value: CantBeSerialized) -> Result<(), JsValue>; + + #[wasm_bindgen(catch, js_name = "noopArray")] + pub fn do_not_serialize_vector(value: Vec) -> Result<(), JsValue>; } #[wasm_bindgen_test] fn test_convert_simple_value_type() { - let val = SimpleData { + let first_value = SimpleData { value: 42, text: "Hello".to_string(), }; - validate_simple_data(val.clone(), &|val_after| { - assert_eq!(val_after, val); + let second_value = SimpleData { + value: 25, + text: "World".to_string(), + }; + + let third_value = SimpleData { + value: 271828, + text: "Haskell".to_string(), + }; + + validate_simple_data(first_value.clone(), &|val_after| { + assert_eq!(val_after, first_value); + }) + .unwrap_throw(); + + validate_simple_data_ref(first_value.clone(), &|val_after| { + assert_eq!(val_after, &first_value); }) .unwrap_throw(); - validate_simple_data_ref(val.clone(), &|val_after| { - assert_eq!(val_after, &val); + let values = vec![ + first_value.clone(), + second_value.clone(), + third_value.clone(), + ]; + validate_array(values, &|values| { + assert_eq!(values.len(), 3); + assert_eq!(values[0], first_value); }) .unwrap_throw(); } @@ -101,3 +140,15 @@ fn test_data_that_cant_be_serialized_throws_an_appropriate_error() { let _ = do_not_serialize(val); } + +#[wasm_bindgen_test] +#[should_panic(expected = "NO_SERIALIZE")] +fn test_vector_of_data_that_cant_be_serialized_throws_an_appropriate_error() { + let first_value = CantBeSerialized { value: 42 }; + let second_value = CantBeSerialized { value: 43 }; + let third_value = CantBeSerialized { value: 44 }; + + let vector = vec![first_value, second_value, third_value]; + + let _ = do_not_serialize_vector(vector); +} From ab8b1eda5c578e32564eda3700ff67bc273840aa Mon Sep 17 00:00:00 2001 From: eneoli Date: Fri, 2 Aug 2024 00:45:03 +0200 Subject: [PATCH 10/12] cleanup --- tests-e2e/test1/entry_point.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests-e2e/test1/entry_point.rs b/tests-e2e/test1/entry_point.rs index 18d4015..afa1a00 100644 --- a/tests-e2e/test1/entry_point.rs +++ b/tests-e2e/test1/entry_point.rs @@ -10,8 +10,7 @@ pub struct Point { } #[wasm_bindgen] -pub fn consume(point: Point) { -} +pub fn consume(point: Point) {} #[wasm_bindgen] pub fn into_js() -> Point { @@ -19,14 +18,13 @@ pub fn into_js() -> Point { } #[wasm_bindgen] -pub fn consume_vector(points: Vec) { -} +pub fn consume_vector(points: Vec) {} #[wasm_bindgen] pub fn vector_into_js() -> Vec { vec![ - Point { x: 1, y: 6 }, - Point { x: 2, y: 5 }, - Point { x: 3, y: 4 }, + Point { x: 1, y: 6 }, + Point { x: 2, y: 5 }, + Point { x: 3, y: 4 }, ] -} \ No newline at end of file +} From b5f73bade8b3b203be8eecfd2f9900eb44bf6056 Mon Sep 17 00:00:00 2001 From: eneoli Date: Sun, 4 Aug 2024 13:04:30 +0200 Subject: [PATCH 11/12] moved new tests into own directory --- tests-e2e/reference_output/test1/test1.d.ts | 12 --------- tests-e2e/reference_output/test4/test1.d.ts | 23 ++++++++++++++++ tests-e2e/test1/entry_point.rs | 15 ----------- tests-e2e/test4/Cargo.toml | 21 +++++++++++++++ tests-e2e/test4/entry_point.rs | 30 +++++++++++++++++++++ 5 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 tests-e2e/reference_output/test4/test1.d.ts create mode 100644 tests-e2e/test4/Cargo.toml create mode 100644 tests-e2e/test4/entry_point.rs diff --git a/tests-e2e/reference_output/test1/test1.d.ts b/tests-e2e/reference_output/test1/test1.d.ts index c7675da..7ac75a5 100644 --- a/tests-e2e/reference_output/test1/test1.d.ts +++ b/tests-e2e/reference_output/test1/test1.d.ts @@ -1,21 +1,9 @@ /* tslint:disable */ /* eslint-disable */ /** -* @param {Point} point -*/ -export function consume(point: Point): void; -/** * @returns {Point} */ export function into_js(): Point; -/** -* @param {(Point)[]} points -*/ -export function consume_vector(points: (Point)[]): void; -/** -* @returns {(Point)[]} -*/ -export function vector_into_js(): (Point)[]; export interface Point { x: number; y: number; diff --git a/tests-e2e/reference_output/test4/test1.d.ts b/tests-e2e/reference_output/test4/test1.d.ts new file mode 100644 index 0000000..c7675da --- /dev/null +++ b/tests-e2e/reference_output/test4/test1.d.ts @@ -0,0 +1,23 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +* @param {Point} point +*/ +export function consume(point: Point): void; +/** +* @returns {Point} +*/ +export function into_js(): Point; +/** +* @param {(Point)[]} points +*/ +export function consume_vector(points: (Point)[]): void; +/** +* @returns {(Point)[]} +*/ +export function vector_into_js(): (Point)[]; +export interface Point { + x: number; + y: number; +} + diff --git a/tests-e2e/test1/entry_point.rs b/tests-e2e/test1/entry_point.rs index afa1a00..8c10c71 100644 --- a/tests-e2e/test1/entry_point.rs +++ b/tests-e2e/test1/entry_point.rs @@ -9,22 +9,7 @@ pub struct Point { y: i32, } -#[wasm_bindgen] -pub fn consume(point: Point) {} - #[wasm_bindgen] pub fn into_js() -> Point { Point { x: 0, y: 0 } } - -#[wasm_bindgen] -pub fn consume_vector(points: Vec) {} - -#[wasm_bindgen] -pub fn vector_into_js() -> Vec { - vec![ - Point { x: 1, y: 6 }, - Point { x: 2, y: 5 }, - Point { x: 3, y: 4 }, - ] -} diff --git a/tests-e2e/test4/Cargo.toml b/tests-e2e/test4/Cargo.toml new file mode 100644 index 0000000..2454f1b --- /dev/null +++ b/tests-e2e/test4/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "test4" +publish = false +version = "0.1.0" +edition = "2021" + +[dependencies] +wasm-bindgen = "0.2" +tsify-next = { path = "../..", version = "*" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[dev-dependencies] +wasm-bindgen-test = "0.3" + +[lib] +path = "entry_point.rs" +crate-type = ["cdylib"] + +[build-dependencies] +wasm-bindgen-cli = "0.2" diff --git a/tests-e2e/test4/entry_point.rs b/tests-e2e/test4/entry_point.rs new file mode 100644 index 0000000..afa1a00 --- /dev/null +++ b/tests-e2e/test4/entry_point.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; +use tsify_next::Tsify; +use wasm_bindgen::prelude::*; + +#[derive(Tsify, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct Point { + x: i32, + y: i32, +} + +#[wasm_bindgen] +pub fn consume(point: Point) {} + +#[wasm_bindgen] +pub fn into_js() -> Point { + Point { x: 0, y: 0 } +} + +#[wasm_bindgen] +pub fn consume_vector(points: Vec) {} + +#[wasm_bindgen] +pub fn vector_into_js() -> Vec { + vec![ + Point { x: 1, y: 6 }, + Point { x: 2, y: 5 }, + Point { x: 3, y: 4 }, + ] +} From 929bb18fc423da1c3dcd93872ce0bbb307a95fc3 Mon Sep 17 00:00:00 2001 From: eneoli Date: Sun, 4 Aug 2024 13:05:57 +0200 Subject: [PATCH 12/12] rename file to test4.d.ts --- tests-e2e/reference_output/test4/{test1.d.ts => test4.d.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests-e2e/reference_output/test4/{test1.d.ts => test4.d.ts} (100%) diff --git a/tests-e2e/reference_output/test4/test1.d.ts b/tests-e2e/reference_output/test4/test4.d.ts similarity index 100% rename from tests-e2e/reference_output/test4/test1.d.ts rename to tests-e2e/reference_output/test4/test4.d.ts