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] diff --git a/tests-e2e/reference_output/test4/test4.d.ts b/tests-e2e/reference_output/test4/test4.d.ts new file mode 100644 index 0000000..c7675da --- /dev/null +++ b/tests-e2e/reference_output/test4/test4.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/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 }, + ] +} diff --git a/tests/expand/borrow.expanded.rs b/tests/expand/borrow.expanded.rs index 14dd9b5..410f218 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,12 +30,20 @@ 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() { ::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, @@ -267,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, @@ -311,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 97730a5..85f09e8 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,12 +31,20 @@ 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() { ::JsType::describe() } } + impl WasmDescribeVector for GenericEnum { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } impl IntoWasmAbi for GenericEnum where GenericEnum: _serde::Serialize, @@ -273,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, @@ -317,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 d8e5957..e0d3598 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,12 +28,20 @@ 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() { ::JsType::describe() } } + impl WasmDescribeVector for GenericStruct { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } impl IntoWasmAbi for GenericStruct where GenericStruct: _serde::Serialize, @@ -272,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, @@ -316,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); @@ -325,172 +237,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,12 +256,20 @@ 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() { ::JsType::describe() } } + impl WasmDescribeVector for GenericNewtype { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } impl IntoWasmAbi for GenericNewtype where GenericNewtype: _serde::Serialize, @@ -589,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, @@ -633,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/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;"; }; 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); +} diff --git a/tsify-next-macros/src/attrs.rs b/tsify-next-macros/src/attrs.rs index 4ef01c3..a2ec69b 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)] @@ -14,8 +12,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. @@ -49,7 +45,6 @@ impl TsifyContainerAttrs { from_wasm_abi: false, namespace: false, ty_config: TypeGenerationConfig::default(), - comments: extract_doc_comments(&input.attrs), }; for attr in &input.attrs { @@ -154,7 +149,6 @@ impl TsifyContainerAttrs { pub struct TsifyFieldAttrs { pub type_override: Option, pub optional: bool, - pub comments: Vec, } impl TsifyFieldAttrs { @@ -162,7 +156,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 { 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 diff --git a/tsify-next-macros/src/wasm_bindgen.rs b/tsify-next-macros/src/wasm_bindgen.rs index e0dc094..d802c29 100644 --- a/tsify-next-macros/src/wasm_bindgen.rs +++ b/tsify-next-macros/src/wasm_bindgen.rs @@ -26,6 +26,13 @@ pub fn expand(cont: &Container, decl: Decl) -> TokenStream { ::JsType::describe() } } + + impl #impl_generics WasmDescribeVector for #ident #ty_generics #where_clause { + #[inline] + fn describe_vector() { + ::JsType::describe_vector() + } + } } }); @@ -37,6 +44,7 @@ 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 from_wasm_abi = attrs.from_wasm_abi.then(|| expand_from_wasm_abi(cont)); @@ -52,8 +60,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::*, }; @@ -144,6 +152,33 @@ fn expand_into_wasm_abi(cont: &Container) -> TokenStream { } } } + + 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. + 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) + } + } } } @@ -204,5 +239,23 @@ fn expand_from_wasm_abi(cont: &Container) -> TokenStream { SelfOwner(result.unwrap_throw()) } } + + 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() + } + } } }