From 0ea522e614621b56cb9ae4af508deccc74b30d52 Mon Sep 17 00:00:00 2001 From: jhugman Date: Tue, 17 Dec 2024 14:51:48 +0000 Subject: [PATCH] Switch from ArrayBuffer to ByteArray (#187) According to [The Big O of Code Reviews](https://www.egorand.dev/the-big-o-of-code-reviews/), this is a O(_n_) change. ubrn lowers most types to an `ArrayBuffer` and then maps it to a `jsi::ArrayBuffer`, and then on to `RustBuffer`. `wasm-bindgen` maps a `Uint8Array` on to a `Vec`. In order to lower types to a `Uint8Array` for WASM and `ArrayBuffer` for JSI, some amount of bundler violence was needed. Given we're using multiple bundlers to package up the frontend Typescript, _and_ we're not really in control of what bundler the developer uses, this doesn't seem like a viable approach. Instead, this PR changes ubrn to lower types down to a `Uint8Array`, so that both WASM and React Native use the same underlying representation of byte arrays. Unfortunately, jsi doesn't expose API for typed arrays (it may in the future), so we look up the [`buffer` property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#instance_properties) to get an underlying `ArrayBuffer`. On the way back, an object of type `{ buffer: ArrayBuffer; }` is returned, which is enough to make the uniffi work and the typescript compiler think it that a typed array has come back from Rust/C++. Most of this PR was mechanical refactoring (e.g. `ArrayBuffer` renamed to `UniffiByteArray`) but there are enough manual changes which merit it being O(_n_). --- CHANGELOG.md | 30 +++++++++++++++ cpp/includes/ForeignBytes.h | 24 ------------ cpp/includes/UniffiByteArray.h | 38 +++++++++++++++++++ cpp/includes/UniffiJsiTypes.h | 1 + cpp/includes/UniffiString.h | 7 +++- .../gen_cpp/templates/RustBufferHelper.cpp | 38 ++++++++++++------- .../src/bindings/gen_typescript/oracle.rs | 6 +-- .../templates/CallbackInterfaceImpl.ts | 3 +- .../gen_typescript/templates/EnumTemplate.ts | 4 +- .../gen_typescript/templates/ErrorTemplate.ts | 4 +- .../templates/RecordTemplate.ts | 4 +- .../gen_typescript/templates/StringHelper.ts | 17 +++++---- .../templates/TaggedEnumTemplate.ts | 4 +- .../bindings/test_callbacks_regression.ts | 2 +- typescript/src/async-callbacks.ts | 19 +++++++--- typescript/src/async-rust-call.ts | 3 +- typescript/src/callbacks.ts | 8 ++-- typescript/src/ffi-converters.ts | 26 ++++++------- typescript/src/ffi-types.ts | 12 +++++- typescript/src/objects.ts | 8 ++-- typescript/src/rust-call.ts | 9 +++-- typescript/tests/ffi-converters.test.ts | 9 ++--- 22 files changed, 177 insertions(+), 99 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 cpp/includes/UniffiByteArray.h diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..955f7aa8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Upcoming releases + +## 🦊 What's Changed +* Switched from passing `ArrayBuffer`s to using `Uint8Array`, to accommodate WASM better. ([#187](https://github.com/jhugman/uniffi-bindgen-react-native/pull/187)) + +**Full Changelog**: https://github.com/jhugman/uniffi-bindgen-react-native/compare/0.28.3-1...main + +# 0.28.3-1 + +This is the first supported release of the `uniffi-bindgen-react-native`. Please hack responsibly. Share and enjoy. + +## 🦊 What's Changed +* Handle type parameter change in crnl 0.45.1 ([#182](https://github.com/jhugman/uniffi-bindgen-react-native/pull/182)) +* Make first run more informative while compiling ([#185](https://github.com/jhugman/uniffi-bindgen-react-native/pull/185)) +* Initial refactor in preparing for WASM ([#174](https://github.com/jhugman/uniffi-bindgen-react-native/pull/174)) +* Add callbacks-example fixture from uniffi-rs ([#172](https://github.com/jhugman/uniffi-bindgen-react-native/pull/172)) +* Fix CLI working without an extension ([#183](https://github.com/jhugman/uniffi-bindgen-react-native/pull/183)) +* Use version released to Cocoapods and npm ([#184](https://github.com/jhugman/uniffi-bindgen-react-native/pull/184)) + +**Full Changelog**: https://github.com/jhugman/uniffi-bindgen-react-native/compare/0.28.3-0...0.28.3-1 + +/* +## ✨ What's New + +## 🦊 What's Changed + +## ⚠️ Breaking Changes + +**Full Changelog**: https://github.com/jhugman/uniffi-bindgen-react-native/compare/{{previous}}...{{current}} +*/ diff --git a/cpp/includes/ForeignBytes.h b/cpp/includes/ForeignBytes.h index 5ffa6819..0278fe41 100644 --- a/cpp/includes/ForeignBytes.h +++ b/cpp/includes/ForeignBytes.h @@ -12,27 +12,3 @@ struct ForeignBytes { int32_t len; uint8_t *data; }; - -namespace uniffi_jsi { -using namespace facebook; - -template <> struct Bridging { - static ForeignBytes fromJs(jsi::Runtime &rt, const jsi::Value &value) { - try { - auto buffer = value.asObject(rt).getArrayBuffer(rt); - return ForeignBytes{ - .len = static_cast(buffer.length(rt)), - .data = buffer.data(rt), - }; - } catch (const std::logic_error &e) { - throw jsi::JSError(rt, e.what()); - } - } - - static jsi::Value toJs(jsi::Runtime &rt, std::shared_ptr, - ForeignBytes value) { - throw jsi::JSError(rt, "Unreachable ForeignBytes.toJs"); - } -}; - -} // namespace uniffi_jsi diff --git a/cpp/includes/UniffiByteArray.h b/cpp/includes/UniffiByteArray.h new file mode 100644 index 00000000..38a49c4f --- /dev/null +++ b/cpp/includes/UniffiByteArray.h @@ -0,0 +1,38 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/ + */ +#pragma once + +#include "Bridging.h" +#include + +namespace uniffi_jsi { +using namespace facebook; + +template <> struct Bridging { + static jsi::ArrayBuffer value_to_arraybuffer(jsi::Runtime &rt, + const jsi::Value &value) { + try { + return value.asObject(rt) + .getPropertyAsObject(rt, "buffer") + .getArrayBuffer(rt); + } catch (const std::logic_error &e) { + throw jsi::JSError(rt, e.what()); + } + } + + static jsi::Value arraybuffer_to_value(jsi::Runtime &rt, + const jsi::ArrayBuffer &arrayBuffer) { + try { + jsi::Object obj(rt); + obj.setProperty(rt, "buffer", arrayBuffer); + return jsi::Value(rt, obj); + } catch (const std::logic_error &e) { + throw jsi::JSError(rt, e.what()); + } + } +}; + +} // namespace uniffi_jsi diff --git a/cpp/includes/UniffiJsiTypes.h b/cpp/includes/UniffiJsiTypes.h index fed9458e..d135c677 100644 --- a/cpp/includes/UniffiJsiTypes.h +++ b/cpp/includes/UniffiJsiTypes.h @@ -21,6 +21,7 @@ #include "ForeignBytes.h" #include "ReferenceHolder.h" #include "RustBuffer.h" +#include "UniffiByteArray.h" #include "UniffiCallInvoker.h" #include "UniffiRustCallStatus.h" #include "UniffiString.h" diff --git a/cpp/includes/UniffiString.h b/cpp/includes/UniffiString.h index 63475447..546b5e14 100644 --- a/cpp/includes/UniffiString.h +++ b/cpp/includes/UniffiString.h @@ -15,7 +15,9 @@ template <> struct Bridging { static jsi::Value arraybuffer_to_string(jsi::Runtime &rt, const jsi::Value &value) { try { - auto buffer = value.asObject(rt).getArrayBuffer(rt); + auto buffer = + uniffi_jsi::Bridging::value_to_arraybuffer(rt, + value); auto string = jsi::String::createFromUtf8(rt, buffer.data(rt), buffer.length(rt)); return jsi::Value(rt, string); @@ -40,7 +42,8 @@ template <> struct Bridging { std::make_shared(CMutableBuffer(bytes, len)); auto arrayBuffer = jsi::ArrayBuffer(rt, payload); - return jsi::Value(rt, arrayBuffer); + return uniffi_jsi::Bridging::arraybuffer_to_value( + rt, arrayBuffer); } catch (const std::logic_error &e) { throw jsi::JSError(rt, e.what()); } diff --git a/crates/ubrn_bindgen/src/bindings/gen_cpp/templates/RustBufferHelper.cpp b/crates/ubrn_bindgen/src/bindings/gen_cpp/templates/RustBufferHelper.cpp index eed80543..99750bb0 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_cpp/templates/RustBufferHelper.cpp +++ b/crates/ubrn_bindgen/src/bindings/gen_cpp/templates/RustBufferHelper.cpp @@ -11,17 +11,34 @@ template <> struct Bridging { ); } + static void rustbuffer_free(RustBuffer buf) { + RustCallStatus status = { UNIFFI_CALL_STATUS_OK }; + {{ ci.ffi_rustbuffer_free().name() }}( + buf, + &status + ); + } + + static RustBuffer rustbuffer_from_bytes(ForeignBytes bytes) { + RustCallStatus status = { UNIFFI_CALL_STATUS_OK }; + return {{ ci.ffi_rustbuffer_from_bytes().name() }}( + bytes, + &status + ); + } + static RustBuffer fromJs(jsi::Runtime &rt, std::shared_ptr, const jsi::Value &value) { try { - auto bytes = uniffi_jsi::Bridging::fromJs(rt, value); + auto buffer = uniffi_jsi::Bridging::value_to_arraybuffer(rt, value); + auto bytes = ForeignBytes{ + .len = static_cast(buffer.length(rt)), + .data = buffer.data(rt), + }; + // This buffer is constructed from foreign bytes. Rust scaffolding copies // the bytes, to make the RustBuffer. - RustCallStatus status = { UNIFFI_CALL_STATUS_OK }; - auto buf = {{ ci.ffi_rustbuffer_from_bytes().name() }}( - bytes, - &status - ); + auto buf = rustbuffer_from_bytes(bytes); // Once it leaves this function, the buffer is immediately passed back // into Rust, where it's used to deserialize into the Rust versions of the // arguments. At that point, the copy is destroyed. @@ -47,15 +64,10 @@ template <> struct Bridging { // Once we have a Javascript version, we no longer need the Rust version, so // we can call into Rust to tell it it's okay to free that memory. - RustCallStatus status = { UNIFFI_CALL_STATUS_OK }; - - {{ ci.ffi_rustbuffer_free().name() }}( - buf, - &status - ); + rustbuffer_free(buf); // Finally, return the ArrayBuffer. - return jsi::Value(rt, arrayBuffer); + return uniffi_jsi::Bridging::arraybuffer_to_value(rt, arrayBuffer);; } }; diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/oracle.rs b/crates/ubrn_bindgen/src/bindings/gen_typescript/oracle.rs index c8dd9aa2..01d74439 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/oracle.rs +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/oracle.rs @@ -70,7 +70,7 @@ impl CodeOracle { FfiType::Float64 => "0.0".to_owned(), FfiType::Float32 => "0.0".to_owned(), FfiType::RustArcPtr(_) => "null".to_owned(), - FfiType::RustBuffer(_) => "/*empty*/ new ArrayBuffer(0)".to_owned(), + FfiType::RustBuffer(_) => "/*empty*/ new Uint8Array(0)".to_owned(), FfiType::Callback(_) => "null".to_owned(), FfiType::RustCallStatus => "uniffiCreateCallStatus()".to_owned(), _ => unimplemented!("ffi_default_value: {ffi_type:?}"), @@ -107,7 +107,7 @@ impl CodeOracle { pub(crate) fn ffi_type_label_for_cpp(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::RustArcPtr(_) => "UniffiRustArcPtr".to_string(), - FfiType::ForeignBytes => "ArrayBuffer".to_string(), + FfiType::ForeignBytes => "Uint8Array".to_string(), FfiType::RustBuffer(_) => "string".to_string(), _ => self.ffi_type_label(ffi_type), } @@ -123,7 +123,7 @@ impl CodeOracle { FfiType::Float64 => "number".to_string(), FfiType::Handle => "bigint".to_string(), FfiType::RustArcPtr(_) => "bigint".to_string(), - FfiType::RustBuffer(_) => "ArrayBuffer".to_string(), + FfiType::RustBuffer(_) => "Uint8Array".to_string(), FfiType::RustCallStatus => "UniffiRustCallStatus".to_string(), FfiType::ForeignBytes => "ForeignBytes".to_string(), FfiType::Callback(name) => self.ffi_callback_name(name), diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/CallbackInterfaceImpl.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/CallbackInterfaceImpl.ts index d0f167a4..fd75ec80 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/CallbackInterfaceImpl.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/CallbackInterfaceImpl.ts @@ -1,5 +1,6 @@ {{- self.import_infra_type("UniffiHandle", "handle-map") }} {{- self.import_infra_type("UniffiReferenceHolder", "callbacks") }} +{{- self.import_infra_type("UniffiByteArray", "ffi-types")}} {{- self.import_infra("UniffiRustCaller", "rust-call")}} {{- self.import_infra_type("UniffiRustCallStatus", "rust-call")}} {{- self.import_infra("RustBuffer", "ffi-types")}} @@ -83,7 +84,7 @@ const {{ trait_impl }}: { vtable: {{ vtable|ffi_type_name }}; register: () => vo } ); }; - const uniffiHandleError = (code: number, errorBuf: ArrayBuffer) => { + const uniffiHandleError = (code: number, errorBuf: UniffiByteArray) => { uniffiFutureCallback( uniffiCallbackData, /* {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }} */{ diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/EnumTemplate.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/EnumTemplate.ts index ab514387..c160bc0c 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/EnumTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/EnumTemplate.ts @@ -1,4 +1,4 @@ -{{- self.import_infra("AbstractFfiConverterArrayBuffer", "ffi-converters") -}} +{{- self.import_infra("AbstractFfiConverterByteArray", "ffi-converters") -}} {{- self.import_infra("FfiConverterInt32", "ffi-converters") -}} {{- self.import_infra("UniffiInternalError", "errors") -}} @@ -18,7 +18,7 @@ export enum {{ type_name }} { const {{ ffi_converter_name }} = (() => { const ordinalConverter = FfiConverterInt32; type TypeName = {{ type_name }}; - class FFIConverter extends AbstractFfiConverterArrayBuffer { + class FFIConverter extends AbstractFfiConverterByteArray { read(from: RustBuffer): TypeName { switch (ordinalConverter.read(from)) { {%- for variant in e.variants() %} diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/ErrorTemplate.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/ErrorTemplate.ts index 0c9d94be..1a2423e0 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/ErrorTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/ErrorTemplate.ts @@ -63,8 +63,8 @@ export const {{ decl_type_name }} = (() => { const {{ ffi_converter_name }} = (() => { const intConverter = FfiConverterInt32; type TypeName = {{ type_name }}; - {{- self.import_infra("AbstractFfiConverterArrayBuffer", "ffi-converters") }} - class FfiConverter extends AbstractFfiConverterArrayBuffer { + {{- self.import_infra("AbstractFfiConverterByteArray", "ffi-converters") }} + class FfiConverter extends AbstractFfiConverterByteArray { read(from: RustBuffer): TypeName { switch (intConverter.read(from)) { {%- for variant in e.variants() %} diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/RecordTemplate.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/RecordTemplate.ts index 1f6a44e0..01a51f7d 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/RecordTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/RecordTemplate.ts @@ -50,8 +50,8 @@ export const {{ decl_type_name }} = (() => { const {{ ffi_converter_name }} = (() => { type TypeName = {{ type_name }}; - {{- self.import_infra("AbstractFfiConverterArrayBuffer", "ffi-converters") }} - class FFIConverter extends AbstractFfiConverterArrayBuffer { + {{- self.import_infra("AbstractFfiConverterByteArray", "ffi-converters") }} + class FFIConverter extends AbstractFfiConverterByteArray { read(from: RustBuffer): TypeName { return { {%- for field in rec.fields() %} diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/StringHelper.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/StringHelper.ts index 24d6c04b..e9c7ec13 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/StringHelper.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/StringHelper.ts @@ -1,11 +1,12 @@ -{{- self.import_infra("UniffiInternalError", "errors") }} {{- self.import_infra("RustBuffer", "ffi-types") }} +{{- self.import_infra_type("UniffiByteArray", "ffi-types") }} {{- self.import_infra("FfiConverterInt32", "ffi-converters") }} {{- self.import_infra_type("FfiConverter", "ffi-converters") }} -const stringToArrayBuffer = (s: string): ArrayBuffer => + +const stringToArrayBuffer = (s: string): UniffiByteArray => uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_arraybuffer()) %}(s, status)); -const arrayBufferToString = (ab: ArrayBuffer): string => +const arrayBufferToString = (ab: UniffiByteArray): string => uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_arraybuffer_to_string()) %}(ab, status)); const stringByteLength = (s: string): number => @@ -14,20 +15,20 @@ const stringByteLength = (s: string): number => const FfiConverterString = (() => { const lengthConverter = FfiConverterInt32; type TypeName = string; - class FFIConverter implements FfiConverter { - lift(value: ArrayBuffer): TypeName { + class FFIConverter implements FfiConverter { + lift(value: UniffiByteArray): TypeName { return arrayBufferToString(value); } - lower(value: TypeName): ArrayBuffer { + lower(value: TypeName): UniffiByteArray { return stringToArrayBuffer(value); } read(from: RustBuffer): TypeName { const length = lengthConverter.read(from); const bytes = from.readBytes(length); - return arrayBufferToString(bytes); + return arrayBufferToString(new Uint8Array(bytes)); } write(value: TypeName, into: RustBuffer): void { - const buffer = stringToArrayBuffer(value); + const buffer = stringToArrayBuffer(value).buffer; const numBytes = buffer.byteLength; lengthConverter.write(numBytes, into); into.writeBytes(buffer); diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/TaggedEnumTemplate.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/TaggedEnumTemplate.ts index a99ba32f..5c0060d2 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/TaggedEnumTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/TaggedEnumTemplate.ts @@ -140,8 +140,8 @@ export const {{ decl_type_name }} = (() => { const {{ ffi_converter_name }} = (() => { const ordinalConverter = FfiConverterInt32; type TypeName = {{ type_name }}; - {{- self.import_infra("AbstractFfiConverterArrayBuffer", "ffi-converters") }} - class FFIConverter extends AbstractFfiConverterArrayBuffer { + {{- self.import_infra("AbstractFfiConverterByteArray", "ffi-converters") }} + class FFIConverter extends AbstractFfiConverterByteArray { read(from: RustBuffer): TypeName { switch (ordinalConverter.read(from)) { {%- for variant in e.variants() %} diff --git a/fixtures/callbacks-regression/tests/bindings/test_callbacks_regression.ts b/fixtures/callbacks-regression/tests/bindings/test_callbacks_regression.ts index 4137f732..ac450068 100644 --- a/fixtures/callbacks-regression/tests/bindings/test_callbacks_regression.ts +++ b/fixtures/callbacks-regression/tests/bindings/test_callbacks_regression.ts @@ -44,7 +44,7 @@ async function testToMax(max: number, t: AsyncAsserts) { } (async () => { - for (let i = 1; i <= 1024; i *= 2) { + for (let i = 1; i <= 512; i *= 2) { await asyncTest( `Full tilt test up to ${i}`, async (t) => await testToMax(i, t), diff --git a/typescript/src/async-callbacks.ts b/typescript/src/async-callbacks.ts index 78499f2c..8bde146d 100644 --- a/typescript/src/async-callbacks.ts +++ b/typescript/src/async-callbacks.ts @@ -5,6 +5,7 @@ */ import { CALL_ERROR, CALL_UNEXPECTED_ERROR } from "./rust-call"; import { type UniffiHandle, UniffiHandleMap } from "./handle-map"; +import { type UniffiByteArray } from "./ffi-types"; // Some additional data we hold for each in-flight promise. type PromiseHelper = { @@ -23,7 +24,7 @@ const UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = new UniffiHandleMap(); // Some degenerate functions used for default arguments. const notExpectedError = (err: any) => false; -function emptyLowerError(e: E): ArrayBuffer { +function emptyLowerError(e: E): UniffiByteArray { throw new Error("Unreachable"); } @@ -38,8 +39,11 @@ export type UniffiForeignFuture = { export function uniffiTraitInterfaceCallAsync( makeCall: (signal: AbortSignal) => Promise, handleSuccess: (value: T) => void, - handleError: (callStatus: /*i8*/ number, errorBuffer: ArrayBuffer) => void, - lowerString: (str: string) => ArrayBuffer, + handleError: ( + callStatus: /*i8*/ number, + errorBuffer: UniffiByteArray, + ) => void, + lowerString: (str: string) => UniffiByteArray, ): UniffiForeignFuture { return uniffiTraitInterfaceCallAsyncWithError( makeCall, @@ -54,10 +58,13 @@ export function uniffiTraitInterfaceCallAsync( export function uniffiTraitInterfaceCallAsyncWithError( makeCall: (signal: AbortSignal) => Promise, handleSuccess: (value: T) => void, - handleError: (callStatus: /*i8*/ number, errorBuffer: ArrayBuffer) => void, + handleError: ( + callStatus: /*i8*/ number, + errorBuffer: UniffiByteArray, + ) => void, isErrorType: (error: any) => boolean, - lowerError: (error: E) => ArrayBuffer, - lowerString: (str: string) => ArrayBuffer, + lowerError: (error: E) => UniffiByteArray, + lowerString: (str: string) => UniffiByteArray, ): UniffiForeignFuture { const settledHolder: { settled: boolean } = { settled: false }; const abortController = new AbortController(); diff --git a/typescript/src/async-rust-call.ts b/typescript/src/async-rust-call.ts index 493ae5d8..4335536a 100644 --- a/typescript/src/async-rust-call.ts +++ b/typescript/src/async-rust-call.ts @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/ */ import { UniffiInternalError } from "./errors"; +import { type UniffiByteArray } from "./ffi-types"; import { UniffiHandleMap, type UniffiHandle } from "./handle-map"; import { type UniffiErrorHandler, @@ -57,7 +58,7 @@ export async function uniffiRustCallAsync( completeFunc: (rustFuture: bigint, status: UniffiRustCallStatus) => F, freeFunc: (rustFuture: bigint) => void, liftFunc: (lower: F) => T, - liftString: (arrayBuffer: ArrayBuffer) => string, + liftString: (bytes: UniffiByteArray) => string, asyncOpts?: { signal: AbortSignal }, errorHandler?: UniffiErrorHandler, ): Promise { diff --git a/typescript/src/callbacks.ts b/typescript/src/callbacks.ts index c127c28b..a7fcaa63 100644 --- a/typescript/src/callbacks.ts +++ b/typescript/src/callbacks.ts @@ -4,7 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/ */ import { type FfiConverter, FfiConverterUInt64 } from "./ffi-converters"; -import { RustBuffer } from "./ffi-types"; +import { type UniffiByteArray, RustBuffer } from "./ffi-types"; import { type UniffiHandle, UniffiHandleMap, @@ -47,7 +47,7 @@ export function uniffiTraitInterfaceCall( callStatus: UniffiRustCallStatus, makeCall: () => T, writeReturn: (v: T) => void, - lowerString: (s: string) => ArrayBuffer, + lowerString: (s: string) => UniffiByteArray, ) { try { writeReturn(makeCall()); @@ -62,8 +62,8 @@ export function uniffiTraitInterfaceCallWithError( makeCall: () => T, writeReturn: (v: T) => void, isErrorType: (e: any) => boolean, - lowerError: (err: E) => ArrayBuffer, - lowerString: (s: string) => ArrayBuffer, + lowerError: (err: E) => UniffiByteArray, + lowerString: (s: string) => UniffiByteArray, ): void { try { writeReturn(makeCall()); diff --git a/typescript/src/ffi-converters.ts b/typescript/src/ffi-converters.ts index 4540de20..05444b1d 100644 --- a/typescript/src/ffi-converters.ts +++ b/typescript/src/ffi-converters.ts @@ -4,7 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/ */ import { UniffiInternalError } from "./errors"; -import { RustBuffer } from "./ffi-types"; +import { type UniffiByteArray, RustBuffer } from "./ffi-types"; // https://github.com/mozilla/uniffi-rs/blob/main/docs/manual/src/internals/lifting_and_lowering.md export interface FfiConverter { @@ -27,17 +27,17 @@ export abstract class FfiConverterPrimitive implements FfiConverter { abstract allocationSize(value: T): number; } -export abstract class AbstractFfiConverterArrayBuffer - implements FfiConverter +export abstract class AbstractFfiConverterByteArray + implements FfiConverter { - lift(value: ArrayBuffer): TsType { - const buffer = RustBuffer.fromArrayBuffer(value); + lift(value: UniffiByteArray): TsType { + const buffer = RustBuffer.fromByteArray(value); return this.read(buffer); } - lower(value: TsType): ArrayBuffer { + lower(value: TsType): UniffiByteArray { const buffer = RustBuffer.withCapacity(this.allocationSize(value)); this.write(value, buffer); - return buffer.arrayBuffer; + return buffer.byteArray; } abstract read(from: RustBuffer): TsType; abstract write(value: TsType, into: RustBuffer): void; @@ -161,7 +161,7 @@ export const FfiConverterDuration = (() => { const nanosConverter = FfiConverterUInt32; const msPerSecBigInt = BigInt("1000"); const nanosPerMs = 1e6; - class FFIConverter extends AbstractFfiConverterArrayBuffer { + class FFIConverter extends AbstractFfiConverterByteArray { read(from: RustBuffer): UniffiDuration { const secsBigInt = secondsConverter.read(from); const nanos = nanosConverter.read(from); @@ -205,7 +205,7 @@ export const FfiConverterTimestamp = (() => { return new Date(ms); } - class FFIConverter extends AbstractFfiConverterArrayBuffer { + class FFIConverter extends AbstractFfiConverterByteArray { read(from: RustBuffer): UniffiTimestamp { const secsBigInt = secondsConverter.read(from); const nanos = nanosConverter.read(from); @@ -233,7 +233,7 @@ export const FfiConverterTimestamp = (() => { return new FFIConverter(); })(); -export class FfiConverterOptional extends AbstractFfiConverterArrayBuffer< +export class FfiConverterOptional extends AbstractFfiConverterByteArray< Item | undefined > { private static flagConverter = FfiConverterBool; @@ -261,7 +261,7 @@ export class FfiConverterOptional extends AbstractFfiConverterArrayBuffer< } } -export class FfiConverterArray extends AbstractFfiConverterArrayBuffer< +export class FfiConverterArray extends AbstractFfiConverterByteArray< Array > { private static sizeConverter = FfiConverterInt32; @@ -291,7 +291,7 @@ export class FfiConverterArray extends AbstractFfiConverterArrayBuffer< } } -export class FfiConverterMap extends AbstractFfiConverterArrayBuffer< +export class FfiConverterMap extends AbstractFfiConverterByteArray< Map > { private static sizeConverter = FfiConverterInt32; @@ -344,7 +344,7 @@ export const FfiConverterArrayBuffer = (() => { return value; } const lengthConverter = FfiConverterInt32; - class FFIConverter extends AbstractFfiConverterArrayBuffer { + class FFIConverter extends AbstractFfiConverterByteArray { read(from: RustBuffer): ArrayBuffer { const length = lengthConverter.read(from); return from.readBytes(length); diff --git a/typescript/src/ffi-types.ts b/typescript/src/ffi-types.ts index 0f6b7f4f..c7dc9538 100644 --- a/typescript/src/ffi-types.ts +++ b/typescript/src/ffi-types.ts @@ -5,6 +5,8 @@ */ import { UniffiInternalError } from "./errors"; +export type UniffiByteArray = Uint8Array; + export class RustBuffer { private readOffset: number = 0; private writeOffset: number = 0; @@ -25,14 +27,22 @@ export class RustBuffer { return this.withCapacity(0); } - static fromArrayBuffer(buf: ArrayBuffer) { + static fromArrayBuffer(buf: ArrayBuffer): RustBuffer { return new RustBuffer(buf); } + static fromByteArray(buf: UniffiByteArray): RustBuffer { + return new RustBuffer(buf.buffer); + } + get length(): number { return this.arrayBuffer.byteLength; } + get byteArray(): UniffiByteArray { + return new Uint8Array(this.arrayBuffer); + } + readBytes(numBytes: number): ArrayBuffer { const start = this.readOffset; const end = this.checkOverflow(start, numBytes); diff --git a/typescript/src/objects.ts b/typescript/src/objects.ts index d5fc086e..0d94d0fe 100644 --- a/typescript/src/objects.ts +++ b/typescript/src/objects.ts @@ -5,7 +5,7 @@ */ import { - AbstractFfiConverterArrayBuffer, + AbstractFfiConverterByteArray, type FfiConverter, FfiConverterUInt64, } from "./ffi-converters"; @@ -130,9 +130,9 @@ export class FfiConverterObjectWithCallbacks extends FfiConverterObject { } /// Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer holding a pointer -export class FfiConverterObjectAsError< - T, -> extends AbstractFfiConverterArrayBuffer> { +export class FfiConverterObjectAsError extends AbstractFfiConverterByteArray< + UniffiThrownObject +> { constructor( private typeName: string, private innerConverter: FfiConverter, diff --git a/typescript/src/rust-call.ts b/typescript/src/rust-call.ts index 40311661..9754e68b 100644 --- a/typescript/src/rust-call.ts +++ b/typescript/src/rust-call.ts @@ -4,19 +4,20 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/ */ import { UniffiInternalError } from "./errors"; +import { type UniffiByteArray } from "./ffi-types"; export const CALL_SUCCESS = 0; export const CALL_ERROR = 1; export const CALL_UNEXPECTED_ERROR = 2; export const CALL_CANCELLED = 3; -type StringLifter = (arrayBuffer: ArrayBuffer) => string; -const emptyStringLifter = (arrayBuffer: ArrayBuffer) => +type StringLifter = (bytes: UniffiByteArray) => string; +const emptyStringLifter = (bytes: UniffiByteArray) => "An error occurred decoding a string"; export type UniffiRustCallStatus = { code: number; - errorBuf?: ArrayBuffer; + errorBuf?: UniffiByteArray; }; export class UniffiRustCaller { constructor( @@ -58,7 +59,7 @@ function uniffiCreateCallStatus(): UniffiRustCallStatus { return { code: CALL_SUCCESS }; } -export type UniffiErrorHandler = (buffer: ArrayBuffer) => Error; +export type UniffiErrorHandler = (buffer: UniffiByteArray) => Error; type RustCallFn = (status: UniffiRustCallStatus) => T; function uniffiCheckCallStatus( diff --git a/typescript/tests/ffi-converters.test.ts b/typescript/tests/ffi-converters.test.ts index a1c42da0..4ce9b332 100644 --- a/typescript/tests/ffi-converters.test.ts +++ b/typescript/tests/ffi-converters.test.ts @@ -7,7 +7,7 @@ import { RustBuffer } from "../src/ffi-types"; import { FfiConverter, FfiConverterArray, - AbstractFfiConverterArrayBuffer, + AbstractFfiConverterByteArray, FfiConverterBool, FfiConverterInt16, FfiConverterInt32, @@ -19,10 +19,7 @@ import { } from "../src/ffi-converters"; import { Asserts, test } from "../testing/asserts"; -class TestConverter< - R extends any, - T, -> extends AbstractFfiConverterArrayBuffer { +class TestConverter extends AbstractFfiConverterByteArray { constructor(public inner: FfiConverter) { super(); } @@ -39,7 +36,7 @@ class TestConverter< function testConverter( t: Asserts, - converter: AbstractFfiConverterArrayBuffer, + converter: AbstractFfiConverterByteArray, input: T, ) { const lowered = converter.lower(input);