From 6d1ac126ed6b9a9840925d42b0dab7809a260540 Mon Sep 17 00:00:00 2001 From: madonoharu Date: Mon, 17 Aug 2026 10:36:42 +0900 Subject: [PATCH 1/3] Release 0.5.7 Raises the tsify-macros floor to 0.5.7 alongside the version bump. The published 0.5.6 macro predates the Clone derive on JsType, which Ts's Clone and its IntoWasmAbi for &Ts both require, so a root-only bump would let an existing lockfile keep resolving a macro that cannot build `&Ts` parameters. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 8 ++++++++ Cargo.toml | 4 ++-- README.md | 2 +- tsify-macros/Cargo.toml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8339fe..814230d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # tsify Changelog +## v0.5.7 + +- Added `Ts`, a wrapper for `#[wasm_bindgen]` parameters and return types. `#[tsify(into_wasm_abi, from_wasm_abi)]` leak memory whenever (de)serialization fails: the conversion happens at the ABI boundary, where the only way to report failure is `wasm_bindgen::throw_str`, which does not run destructors. `Ts` moves the conversion into the function body, where it is an ordinary `Result`. Resolves #65, #47 and #86. @cormacrelf contributed #71 +- Deprecated `into_wasm_abi` and `from_wasm_abi` in favour of `Ts`. The README explains the mechanism; the attributes still work, and no removal is planned +- `Ts` can now be returned from `async fn`. @hgiesel contributed #84 +- `#[tsify(namespace)]` enums now emit `export type E = E.A | E.B` instead of repeating each variant's shape in the union. @hgiesel contributed #78 +- Fixed raw string artifacts in doc comments copied into the generated TypeScript. @samkearney contributed the fix + ## v0.5.6 - Resolve the issue with default parameters in generics diff --git a/Cargo.toml b/Cargo.toml index 795dce6..d067d52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tsify" -version = "0.5.6" +version = "0.5.7" edition = "2021" authors = [ "Madono Haru ", @@ -14,7 +14,7 @@ keywords = ["wasm", "wasm-bindgen", "typescript"] categories = ["wasm"] [dependencies] -tsify-macros = { path = "tsify-macros", version = "0.5.5" } +tsify-macros = { path = "tsify-macros", version = "0.5.7" } wasm-bindgen = { version = "0.2.104", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0", optional = true } diff --git a/README.md b/README.md index 5105d14..8a35a25 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Click to show Cargo.toml. ```toml [dependencies] -tsify = "0.5.5" +tsify = "0.5.7" serde = { version = "1.0", features = ["derive"] } wasm-bindgen = { version = "0.2" } ``` diff --git a/tsify-macros/Cargo.toml b/tsify-macros/Cargo.toml index f0f97e1..2d6e681 100644 --- a/tsify-macros/Cargo.toml +++ b/tsify-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tsify-macros" -version = "0.5.6" +version = "0.5.7" edition = "2021" authors = [ "Madono Haru ", From 8e9050fc503eed01bc50aad70b7f7d007af795ad Mon Sep 17 00:00:00 2001 From: madonoharu Date: Mon, 17 Aug 2026 18:33:05 +0900 Subject: [PATCH 2/3] Fix the borrowed-handle leak and correct the release docs Review of the release candidate turned up a leak in the type whose purpose is to avoid them: IntoWasmAbi for &Ts cloned the JS handle, but the shim generated for a borrowed argument never releases it. wasm-bindgen's own extern types pass the handle by reference for exactly this reason; match that. The Clone bound falls away with it. Docs corrected against what the code actually does: only from_wasm_abi reaches throw_str (into_wasm_abi panics), and the README's namespace enum and .d.ts examples predated the output they describe. Both are now what a build emits. The e2e gate was weaker than it looked: build_all.sh let a failed wasm-pack build through, compare_output.sh skipped references whose counterpart was missing, and a generics test asserted the wrong type. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 5 +++-- README.md | 19 +++++-------------- src/lib.rs | 3 +++ src/ts.rs | 13 +++++++------ tests-e2e/build_all.sh | 5 +++++ tests-e2e/reference_output/compare_output.sh | 5 +++++ tests/generics.rs | 4 ++-- 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 814230d..405b3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## v0.5.7 -- Added `Ts`, a wrapper for `#[wasm_bindgen]` parameters and return types. `#[tsify(into_wasm_abi, from_wasm_abi)]` leak memory whenever (de)serialization fails: the conversion happens at the ABI boundary, where the only way to report failure is `wasm_bindgen::throw_str`, which does not run destructors. `Ts` moves the conversion into the function body, where it is an ordinary `Result`. Resolves #65, #47 and #86. @cormacrelf contributed #71 -- Deprecated `into_wasm_abi` and `from_wasm_abi` in favour of `Ts`. The README explains the mechanism; the attributes still work, and no removal is planned +- Added `Ts`, a wrapper for `#[wasm_bindgen]` parameters and return types. `#[tsify(from_wasm_abi)]` deserializes at the ABI boundary, which cannot report failure, so bad input from JavaScript ends in `wasm_bindgen::throw_str` — a catchable JS exception that skips destructors, leaking a little on every failure until the instance dies. `Ts` keeps the boundary infallible and moves the conversion into the function body, where it is an ordinary `Result`. Resolves #65, #47 and #86. @cormacrelf contributed #71 +- Deprecated `into_wasm_abi` and `from_wasm_abi` in favour of `Ts`. `into_wasm_abi` panics rather than leaks on failure, but it has the same root cause and the same fix. The attributes still work, and no removal is planned; see the README for details +- Fixed a leak in `IntoWasmAbi for &Ts`, which cloned the JS handle for a borrowed argument that the generated shim never releases - `Ts` can now be returned from `async fn`. @hgiesel contributed #84 - `#[tsify(namespace)]` enums now emit `export type E = E.A | E.B` instead of repeating each variant's shape in the union. @hgiesel contributed #78 - Fixed raw string artifacts in doc comments copied into the generated TypeScript. @samkearney contributed the fix diff --git a/README.md b/README.md index 8a35a25..a692ba0 100644 --- a/README.md +++ b/README.md @@ -53,18 +53,14 @@ Will generate the following `.d.ts` file: ```ts /* tslint:disable */ /* eslint-disable */ -/** - * @returns {Point} - */ -export function into_js(): Point; -/** - * @param {Point} point - */ -export function from_js(point: Point): void; export interface Point { x: number; y: number; } + +export function from_js(point: Point): void; + +export function into_js(): Point; ``` This is the behavior due to [`typescript_custom_section`](https://rustwasm.github.io/docs/wasm-bindgen/reference/attributes/on-rust-exports/typescript_custom_section.html) and [`Rust Type conversions`](https://rustwasm.github.io/docs/wasm-bindgen/contributing/design/rust-type-conversions.html). @@ -233,12 +229,7 @@ declare namespace Color { }; } -export type Color = - | "Red" - | "Blue" - | "Green" - | { Rgb: [number, number, number] } - | { Hsv: { hue: number; saturation: number; value: number } }; +export type Color = Color.Red | Color.Blue | Color.Green | Color.Rgb | Color.Hsv; ``` ## Type Aliases diff --git a/src/lib.rs b/src/lib.rs index 4f5d12c..f50d0a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,9 @@ pub struct SerializationConfig { /// `Tsify` is a trait that allows you to convert a type to and from JavaScript. /// Can be implemented manually if you need to customize the serialization or deserialization. pub trait Tsify { + /// Must be a type imported through `#[wasm_bindgen] extern "C" { .. }`. + /// [`Ts`] is `#[repr(transparent)]` over this and passes it across the + /// ABI as a plain JS handle, which any other representation would break. #[cfg(feature = "wasm-bindgen")] type JsType: JsCast; diff --git a/src/ts.rs b/src/ts.rs index c05278d..ab177ec 100644 --- a/src/ts.rs +++ b/src/ts.rs @@ -40,7 +40,6 @@ use wasm_bindgen::{JsCast, JsValue}; /// y: f64, /// } /// -/// /// The panicking version /// #[wasm_bindgen] /// pub fn rotate(v: Ts, theta_rad: f64) -> Result, JsError> { /// // Deserialize to rust type, throw deserialization error if fails @@ -69,7 +68,7 @@ where Self(js, std::marker::PhantomData) } - /// Returns the inner JsValue representation. This is a zero cost operation. + /// Returns the inner JsValue representation, cloning the JS handle. pub fn js_value(&self) -> JsValue where ::JsType: JsCast, @@ -148,14 +147,16 @@ where self.0.into_abi() } } -impl IntoWasmAbi for &Ts +impl<'a, T> IntoWasmAbi for &'a Ts where T: Tsify, - ::JsType: IntoWasmAbi + Clone, + ::JsType: JsCast + WasmDescribe, { - type Abi = ::Abi; + type Abi = <&'a JsValue as IntoWasmAbi>::Abi; + // Borrowed, so the handle stays owned here. Cloning it would hand JS an + // extra table slot that the borrowed-argument shim never releases. fn into_abi(self) -> Self::Abi { - self.0.clone().into_abi() + self.0.unchecked_ref::().into_abi() } } impl FromWasmAbi for Ts diff --git a/tests-e2e/build_all.sh b/tests-e2e/build_all.sh index e654ade..0af5824 100755 --- a/tests-e2e/build_all.sh +++ b/tests-e2e/build_all.sh @@ -1,5 +1,10 @@ #!/bin/bash +# Without this a failed `wasm-pack build` is followed by a successful `popd`, +# so the script reports success and compare_output.sh then compares stale +# artifacts. +set -e + # Define the root directory for the search ROOT_DIR="tests-e2e" diff --git a/tests-e2e/reference_output/compare_output.sh b/tests-e2e/reference_output/compare_output.sh index 41da919..76b68bd 100755 --- a/tests-e2e/reference_output/compare_output.sh +++ b/tests-e2e/reference_output/compare_output.sh @@ -38,6 +38,11 @@ for FOLDERNAME in $(find . -maxdepth 1 -type d); do else echo " Files are identical" fi + else + # A reference with no counterpart means the build stopped emitting + # it; skipping would report that regression as success. + echo "Missing generated file: $OTHER_FILE" + DIFF_FOUND=1 fi done done diff --git a/tests/generics.rs b/tests/generics.rs index 0572c9b..8a88576 100644 --- a/tests/generics.rs +++ b/tests/generics.rs @@ -161,10 +161,10 @@ fn test_generics_with_default_params() { struct DeNamedTuple(A, B, C); let expected = indoc! {r#" - export type SerNamedTuple = [A, B, C];"# + export type DeNamedTuple = [A, B, C];"# }; - assert_eq!(SerNamedTuple::<(), (), ()>::DECL, expected); + assert_eq!(DeNamedTuple::<(), (), ()>::DECL, expected); #[derive(Serialize, Tsify)] #[tsify(into_wasm_abi)] From 9525bf347632a25807df10e02efd81b371f18f2c Mon Sep 17 00:00:00 2001 From: madonoharu Date: Thu, 20 Aug 2026 13:00:32 +0900 Subject: [PATCH 3/3] Drop the changelog entry for a never-released fix, match the sample output The &Ts leak was introduced and fixed between releases, so listing it implies 0.5.6 shipped it. "Addresses" instead of "Resolves": #47 keeps an upstream remainder and #86 an ergonomics one. The .d.ts sample now matches a build byte for byte. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 3 +-- README.md | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 405b3d7..4477efb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,8 @@ ## v0.5.7 -- Added `Ts`, a wrapper for `#[wasm_bindgen]` parameters and return types. `#[tsify(from_wasm_abi)]` deserializes at the ABI boundary, which cannot report failure, so bad input from JavaScript ends in `wasm_bindgen::throw_str` — a catchable JS exception that skips destructors, leaking a little on every failure until the instance dies. `Ts` keeps the boundary infallible and moves the conversion into the function body, where it is an ordinary `Result`. Resolves #65, #47 and #86. @cormacrelf contributed #71 +- Added `Ts`, a wrapper for `#[wasm_bindgen]` parameters and return types. `#[tsify(from_wasm_abi)]` deserializes at the ABI boundary, which cannot report failure, so bad input from JavaScript ends in `wasm_bindgen::throw_str` — a catchable JS exception that skips destructors, leaking a little on every failure until the instance dies. `Ts` keeps the boundary infallible and moves the conversion into the function body, where it is an ordinary `Result`. Addresses #65, #47 and #86. @cormacrelf contributed #71 - Deprecated `into_wasm_abi` and `from_wasm_abi` in favour of `Ts`. `into_wasm_abi` panics rather than leaks on failure, but it has the same root cause and the same fix. The attributes still work, and no removal is planned; see the README for details -- Fixed a leak in `IntoWasmAbi for &Ts`, which cloned the JS handle for a borrowed argument that the generated shim never releases - `Ts` can now be returned from `async fn`. @hgiesel contributed #84 - `#[tsify(namespace)]` enums now emit `export type E = E.A | E.B` instead of repeating each variant's shape in the union. @hgiesel contributed #78 - Fixed raw string artifacts in doc comments copied into the generated TypeScript. @samkearney contributed the fix diff --git a/README.md b/README.md index a692ba0..e43ff8a 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ export interface Point { y: number; } + export function from_js(point: Point): void; export function into_js(): Point;