From ff83e805087475bed6a7a9235027f84a645dea92 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Mon, 16 Dec 2024 15:29:47 +0000 Subject: [PATCH 1/7] Enable coverall2 for wasm --- crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs | 6 ++++-- crates/uniffi_wasm/src/lib.rs | 4 +++- fixtures/coverall2/tests/bindings/.supported-flavors.txt | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 fixtures/coverall2/tests/bindings/.supported-flavors.txt diff --git a/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs b/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs index 165f96e5..69ac1042 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs @@ -290,8 +290,8 @@ impl<'a> ComponentTemplate<'a> { FfiType::Float32 => quote! { #runtime::Float32 }, FfiType::Float64 => quote! { #runtime::Float64 }, FfiType::RustArcPtr(_) => quote! { #runtime::VoidPointer }, - FfiType::RustBuffer(_external_ffi_metadata) => quote! { #runtime::ForeignBytes }, - FfiType::ForeignBytes => quote! { #module::ForeignBytes }, + FfiType::RustBuffer(_) => quote! { #runtime::ForeignBytes }, + FfiType::ForeignBytes => quote! { #runtime::ForeignBytes }, FfiType::Callback(_) => quote! { #module::Callback }, FfiType::Struct(_) => quote! { #module::Struct }, FfiType::Handle => quote! { #module::Handle }, @@ -302,6 +302,7 @@ impl<'a> ComponentTemplate<'a> { } fn ffi_type_rust(&self, t: &FfiType) -> TokenStream { + let uniffi = self.uniffi_ident(); match t { FfiType::UInt8 => quote! { u8 }, FfiType::Int8 => quote! { i8 }, @@ -313,6 +314,7 @@ impl<'a> ComponentTemplate<'a> { FfiType::Int64 => quote! { i64 }, FfiType::Float32 => quote! { f32 }, FfiType::Float64 => quote! { f64 }, + FfiType::RustBuffer(_) => quote! { #uniffi::RustBuffer }, _ => todo!(), } } diff --git a/crates/uniffi_wasm/src/lib.rs b/crates/uniffi_wasm/src/lib.rs index a57642d0..ba67cc39 100644 --- a/crates/uniffi_wasm/src/lib.rs +++ b/crates/uniffi_wasm/src/lib.rs @@ -6,7 +6,9 @@ pub use wasm_bindgen::prelude::wasm_bindgen as export; use wasm_bindgen::prelude::*; -pub use uniffi::RustCallStatus; +pub mod uniffi { + pub use uniffi::{RustBuffer, RustCallStatus}; +} pub trait IntoRust { fn into_rust(v: HighLevel) -> Self; diff --git a/fixtures/coverall2/tests/bindings/.supported-flavors.txt b/fixtures/coverall2/tests/bindings/.supported-flavors.txt new file mode 100644 index 00000000..c7254cd9 --- /dev/null +++ b/fixtures/coverall2/tests/bindings/.supported-flavors.txt @@ -0,0 +1,2 @@ +wasm +jsi From fef54b746c094e5e9622d9c751f30532b90b31e5 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Tue, 17 Dec 2024 17:27:45 +0000 Subject: [PATCH 2/7] Enable strings for WASM --- .../src/bindings/gen_typescript/mod.rs | 4 ++ .../templates/CallbackInterfaceImpl.ts | 1 - .../templates/RecordTemplate.ts | 1 - .../gen_typescript/templates/StringHelper.ts | 60 +++++++------------ .../gen_typescript/templates/Types.ts | 1 + typescript/src/ffi-converters.ts | 47 ++++++++++++++- typescript/src/ffi-types.ts | 21 ++++++- 7 files changed, 90 insertions(+), 45 deletions(-) diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs b/crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs index afac1e98..f93b1d80 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs @@ -82,6 +82,10 @@ impl TsFlavorParams<'_> { pub(crate) fn is_jsi(&self) -> bool { matches!(self.inner, &AbiFlavor::Jsi) } + + pub(crate) fn supports_text_encoder(&self) -> bool { + !matches!(self.inner, &AbiFlavor::Jsi) + } } #[derive(Template)] 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 fd75ec80..bd3211a2 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/CallbackInterfaceImpl.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/CallbackInterfaceImpl.ts @@ -3,7 +3,6 @@ {{- 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")}} {%- let vtable_methods = cbi.vtable_methods() %} {%- let trait_impl = format!("uniffiCallbackInterface{}", name) %} 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 01a51f7d..9709f6a9 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/RecordTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/RecordTemplate.ts @@ -1,4 +1,3 @@ -{{- self.import_infra("RustBuffer", "ffi-types") }} {{- self.import_infra("uniffiCreateRecord", "records") }} {%- let rec = ci|get_record_definition(name) %} 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 e9c7ec13..5904deab 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/StringHelper.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/StringHelper.ts @@ -1,42 +1,24 @@ -{{- 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") }} +{{- self.import_infra("uniffiCreateFfiConverterString", "ffi-converters") }} -const stringToArrayBuffer = (s: string): UniffiByteArray => - uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_arraybuffer()) %}(s, status)); - -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 => - uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_bytelength()) %}(s, status)); - -const FfiConverterString = (() => { - const lengthConverter = FfiConverterInt32; - type TypeName = string; - class FFIConverter implements FfiConverter { - lift(value: UniffiByteArray): TypeName { - return arrayBufferToString(value); - } - lower(value: TypeName): UniffiByteArray { - return stringToArrayBuffer(value); - } - read(from: RustBuffer): TypeName { - const length = lengthConverter.read(from); - const bytes = from.readBytes(length); - return arrayBufferToString(new Uint8Array(bytes)); - } - write(value: TypeName, into: RustBuffer): void { - const buffer = stringToArrayBuffer(value).buffer; - const numBytes = buffer.byteLength; - lengthConverter.write(numBytes, into); - into.writeBytes(buffer); - } - allocationSize(value: TypeName): number { - return lengthConverter.allocationSize(0) + stringByteLength(value); - } - } - - return new FFIConverter(); +{%- if flavor.supports_text_encoder() %} +const stringConverter = (() => { + const encoder = new TextEncoder(); + const decoder = new TextDecoder(); + return { + stringToBytes: (s: string) => encoder.encode(s), + bytesToString: (ab: UniffiByteArray) => decoder.decode(ab), + stringByteLength: (s: string) => encoder.encode(s).byteLength, + }; })(); +{%- else %} +const stringConverter = { + stringToBytes: (s: string) => + uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_arraybuffer()) %}(s, status)), + bytesToString: (ab: UniffiByteArray) => + uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_arraybuffer_to_string()) %}(ab, status)), + stringByteLength: (s: string) => + uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_bytelength()) %}(s, status)), +}; +{%- endif %} +const FfiConverterString = uniffiCreateFfiConverterString(stringConverter); diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/Types.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/Types.ts index 036d3782..a379cb31 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/Types.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/Types.ts @@ -1,4 +1,5 @@ {%- import "macros.ts" as ts %} +{{- self.import_infra("RustBuffer", "ffi-types") }} {{- self.import_infra("UniffiInternalError", "errors") -}} {{- self.import_infra("UniffiRustCaller", "rust-call") }} diff --git a/typescript/src/ffi-converters.ts b/typescript/src/ffi-converters.ts index 05444b1d..b757e3d3 100644 --- a/typescript/src/ffi-converters.ts +++ b/typescript/src/ffi-converters.ts @@ -347,12 +347,12 @@ export const FfiConverterArrayBuffer = (() => { class FFIConverter extends AbstractFfiConverterByteArray { read(from: RustBuffer): ArrayBuffer { const length = lengthConverter.read(from); - return from.readBytes(length); + return from.readArrayBuffer(length); } write(value: ArrayBuffer, into: RustBuffer): void { const length = value.byteLength; lengthConverter.write(length, into); - into.writeBytes(unwrapBuffer(value)); + into.writeByteArray(new Uint8Array(unwrapBuffer(value))); } allocationSize(value: ArrayBuffer): number { return lengthConverter.allocationSize(0) + value.byteLength; @@ -360,3 +360,46 @@ export const FfiConverterArrayBuffer = (() => { } return new FFIConverter(); })(); + +type StringConverter = { + stringToBytes: (s: string) => UniffiByteArray; + bytesToString: (ab: UniffiByteArray) => string; + stringByteLength: (s: string) => number; +}; +export function uniffiCreateFfiConverterString( + converter: StringConverter, +): FfiConverter { + const lengthConverter = FfiConverterInt32; + + class FFIConverter implements FfiConverter { + lift(value: UniffiByteArray): string { + return converter.bytesToString(value); + } + lower(value: string): UniffiByteArray { + return converter.stringToBytes(value); + } + read(from: RustBuffer): string { + const length = lengthConverter.read(from); + // TODO Currently, RustBufferHelper.cpp is pretty dumb, + // and copies all the bytes in the underlying ArrayBuffer. + // Making a better shim for Uint8Array would allow us to use + // readByteArray here, and eliminate a copy. + const bytes = from.readArrayBuffer(length); + return converter.bytesToString(new Uint8Array(bytes)); + } + write(value: string, into: RustBuffer): void { + // TODO: work on RustBufferHelper.cpp is needed to avoid + // the extra copy and use writeByteArray. + const buffer = converter.stringToBytes(value).buffer; + const numBytes = buffer.byteLength; + lengthConverter.write(numBytes, into); + into.writeArrayBuffer(buffer); + } + allocationSize(value: string): number { + return ( + lengthConverter.allocationSize(0) + converter.stringByteLength(value) + ); + } + } + return new FFIConverter(); +} diff --git a/typescript/src/ffi-types.ts b/typescript/src/ffi-types.ts index c7dc9538..2384696d 100644 --- a/typescript/src/ffi-types.ts +++ b/typescript/src/ffi-types.ts @@ -43,7 +43,7 @@ export class RustBuffer { return new Uint8Array(this.arrayBuffer); } - readBytes(numBytes: number): ArrayBuffer { + readArrayBuffer(numBytes: number): ArrayBuffer { const start = this.readOffset; const end = this.checkOverflow(start, numBytes); const value = this.arrayBuffer.slice(start, end); @@ -51,7 +51,15 @@ export class RustBuffer { return value; } - writeBytes(buffer: ArrayBuffer) { + readByteArray(numBytes: number): UniffiByteArray { + const start = this.readOffset; + const end = this.checkOverflow(start, numBytes); + const value = new Uint8Array(this.arrayBuffer, start, numBytes); + this.readOffset = end; + return value; + } + + writeArrayBuffer(buffer: ArrayBuffer) { const start = this.writeOffset; const end = this.checkOverflow(start, buffer.byteLength); @@ -62,6 +70,15 @@ export class RustBuffer { this.writeOffset = end; } + writeByteArray(src: UniffiByteArray) { + const start = this.writeOffset; + const end = this.checkOverflow(start, src.byteLength); + const dest = new Uint8Array(this.arrayBuffer, start); + dest.set(src); + + this.writeOffset = end; + } + readWithView(numBytes: number, reader: (view: DataView) => T): T { const start = this.readOffset; const end = this.checkOverflow(start, numBytes); From 9056f799208f79ec94fe3665774cac0be96accbe Mon Sep 17 00:00:00 2001 From: James Hugman Date: Wed, 18 Dec 2024 13:28:21 +0000 Subject: [PATCH 3/7] Add rondpoint-procmacro --- fixtures/rondpoint-procmacro/Cargo.toml | 19 + fixtures/rondpoint-procmacro/src/lib.rs | 342 +++++++++++++++++ .../tests/bindings/.supported-flavors.txt | 2 + .../bindings/test_rondpoint_procmacro.ts | 359 ++++++++++++++++++ .../tests/test_generated_bindings.rs | 5 + 5 files changed, 727 insertions(+) create mode 100644 fixtures/rondpoint-procmacro/Cargo.toml create mode 100644 fixtures/rondpoint-procmacro/src/lib.rs create mode 100644 fixtures/rondpoint-procmacro/tests/bindings/.supported-flavors.txt create mode 100644 fixtures/rondpoint-procmacro/tests/bindings/test_rondpoint_procmacro.ts create mode 100644 fixtures/rondpoint-procmacro/tests/test_generated_bindings.rs diff --git a/fixtures/rondpoint-procmacro/Cargo.toml b/fixtures/rondpoint-procmacro/Cargo.toml new file mode 100644 index 00000000..cfaf496c --- /dev/null +++ b/fixtures/rondpoint-procmacro/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "uniffi-example-rondpoint-procmacro" +edition = "2021" +version = "0.22.0" +license = "MPL-2.0" +publish = false + +[lib] +crate-type = ["lib", "staticlib", "cdylib"] +name = "uniffi_rondpointpm" + +[dependencies] +uniffi = { workspace = true } + +[build-dependencies] +uniffi = { workspace = true, features = ["build"] } + +[dev-dependencies] +uniffi = { workspace = true, features = ["bindgen-tests"] } diff --git a/fixtures/rondpoint-procmacro/src/lib.rs b/fixtures/rondpoint-procmacro/src/lib.rs new file mode 100644 index 00000000..2b385bd6 --- /dev/null +++ b/fixtures/rondpoint-procmacro/src/lib.rs @@ -0,0 +1,342 @@ +/* + * 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/ + */ + +use std::collections::HashMap; + +uniffi::setup_scaffolding!(); + +#[derive(Debug, Clone, uniffi::Record)] +pub struct Dictionnaire { + un: Enumeration, + deux: bool, + petit_nombre: u8, + gros_nombre: u64, +} + +#[derive(Debug, Clone, uniffi::Record)] +pub struct DictionnaireNombres { + petit_nombre: u8, + court_nombre: u16, + nombre_simple: u32, + gros_nombre: u64, +} + +#[derive(Debug, Clone, uniffi::Record)] +pub struct DictionnaireNombresSignes { + petit_nombre: i8, + court_nombre: i16, + nombre_simple: i32, + gros_nombre: i64, +} + +#[derive(Debug, Clone, uniffi::Enum)] +pub enum Enumeration { + Un, + Deux, + Trois, +} + +#[derive(Debug, Clone, uniffi::Enum)] +pub enum EnumerationAvecDonnees { + Zero, + Un { premier: u32 }, + Deux { premier: u32, second: String }, +} + +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +#[derive(uniffi::Record)] +pub struct minusculeMAJUSCULEDict { + minusculeMAJUSCULEField: bool, +} + +#[allow(non_camel_case_types)] +#[derive(uniffi::Enum)] +pub enum minusculeMAJUSCULEEnum { + minusculeMAJUSCULEVariant, +} + +#[uniffi::export] +fn copie_enumeration(e: Enumeration) -> Enumeration { + e +} + +#[uniffi::export] +fn copie_enumerations(e: Vec) -> Vec { + e +} + +#[uniffi::export] +fn copie_carte( + e: HashMap, +) -> HashMap { + e +} + +#[uniffi::export] +fn copie_dictionnaire(d: Dictionnaire) -> Dictionnaire { + d +} + +#[uniffi::export] +fn switcheroo(b: bool) -> bool { + !b +} + +// Test that values can traverse both ways across the FFI. +// Even if roundtripping works, it's possible we have +// symmetrical errors that cancel each other out. +#[derive(Debug, Clone, uniffi::Object)] +pub struct Retourneur; + +#[uniffi::export] +impl Retourneur { + #[uniffi::constructor] + fn new() -> Self { + Retourneur + } + fn identique_i8(&self, value: i8) -> i8 { + value + } + fn identique_u8(&self, value: u8) -> u8 { + value + } + fn identique_i16(&self, value: i16) -> i16 { + value + } + fn identique_u16(&self, value: u16) -> u16 { + value + } + fn identique_i32(&self, value: i32) -> i32 { + value + } + fn identique_u32(&self, value: u32) -> u32 { + value + } + fn identique_i64(&self, value: i64) -> i64 { + value + } + fn identique_u64(&self, value: u64) -> u64 { + value + } + fn identique_float(&self, value: f32) -> f32 { + value + } + fn identique_double(&self, value: f64) -> f64 { + value + } + fn identique_boolean(&self, value: bool) -> bool { + value + } + fn identique_string(&self, value: String) -> String { + value + } + fn identique_nombres_signes( + &self, + value: DictionnaireNombresSignes, + ) -> DictionnaireNombresSignes { + value + } + fn identique_nombres(&self, value: DictionnaireNombres) -> DictionnaireNombres { + value + } + fn identique_optionneur_dictionnaire( + &self, + value: OptionneurDictionnaire, + ) -> OptionneurDictionnaire { + value + } +} + +#[derive(Debug, Clone, uniffi::Object)] +pub struct Stringifier; + +#[uniffi::export] +impl Stringifier { + #[uniffi::constructor] + fn new() -> Self { + Stringifier + } + fn to_string_i8(&self, value: i8) -> String { + value.to_string() + } + fn to_string_u8(&self, value: u8) -> String { + value.to_string() + } + fn to_string_i16(&self, value: i16) -> String { + value.to_string() + } + fn to_string_u16(&self, value: u16) -> String { + value.to_string() + } + fn to_string_i32(&self, value: i32) -> String { + value.to_string() + } + fn to_string_u32(&self, value: u32) -> String { + value.to_string() + } + fn to_string_i64(&self, value: i64) -> String { + value.to_string() + } + fn to_string_u64(&self, value: u64) -> String { + value.to_string() + } + fn to_string_float(&self, value: f32) -> String { + value.to_string() + } + fn to_string_double(&self, value: f64) -> String { + value.to_string() + } + fn to_string_boolean(&self, value: bool) -> String { + value.to_string() + } + fn well_known_string(&self, value: String) -> String { + format!("uniffi ๐Ÿ’š {value}!") + } +} + +#[derive(Debug, Clone, uniffi::Object)] +pub struct Optionneur; +#[uniffi::export] +impl Optionneur { + #[uniffi::constructor] + fn new() -> Self { + Optionneur + } + #[uniffi::method(default(value = "default"))] + fn sinon_string(&self, value: String) -> String { + value + } + #[uniffi::method(default(value = None))] + fn sinon_null(&self, value: Option) -> Option { + value + } + #[uniffi::method(default(value = false))] + fn sinon_boolean(&self, value: bool) -> bool { + value + } + #[uniffi::method(default(value = []))] + fn sinon_sequence(&self, value: Vec) -> Vec { + value + } + #[uniffi::method(default(value = Some(0)))] + fn sinon_zero(&self, value: Option) -> Option { + value + } + + #[uniffi::method(default(value = 42))] + fn sinon_u8_dec(&self, value: u8) -> u8 { + value + } + #[uniffi::method(default(value = -42))] + fn sinon_i8_dec(&self, value: i8) -> i8 { + value + } + #[uniffi::method(default(value = 42))] + fn sinon_u16_dec(&self, value: u16) -> u16 { + value + } + #[uniffi::method(default(value = 42))] + fn sinon_i16_dec(&self, value: i16) -> i16 { + value + } + #[uniffi::method(default(value = 42))] + fn sinon_u32_dec(&self, value: u32) -> u32 { + value + } + #[uniffi::method(default(value = 42))] + fn sinon_i32_dec(&self, value: i32) -> i32 { + value + } + #[uniffi::method(default(value = 42))] + fn sinon_u64_dec(&self, value: u64) -> u64 { + value + } + #[uniffi::method(default(value = 42))] + fn sinon_i64_dec(&self, value: i64) -> i64 { + value + } + #[uniffi::method(default(value = 0xff))] + fn sinon_u8_hex(&self, value: u8) -> u8 { + value + } + #[uniffi::method(default(value = -0x7f))] + fn sinon_i8_hex(&self, value: i8) -> i8 { + value + } + #[uniffi::method(default(value = 0x7f))] + fn sinon_u16_hex(&self, value: u16) -> u16 { + value + } + #[uniffi::method(default(value = 0x7f))] + fn sinon_i16_hex(&self, value: i16) -> i16 { + value + } + #[uniffi::method(default(value = 0xffffffff))] + fn sinon_u32_hex(&self, value: u32) -> u32 { + value + } + #[uniffi::method(default(value = 0x7fffffff))] + fn sinon_i32_hex(&self, value: i32) -> i32 { + value + } + #[uniffi::method(default(value = 0xffffffffffffffff))] + fn sinon_u64_hex(&self, value: u64) -> u64 { + value + } + #[uniffi::method(default(value = 0x7fffffffffffffff))] + fn sinon_i64_hex(&self, value: i64) -> i64 { + value + } + #[uniffi::method(default(value = 493))] + fn sinon_u32_oct(&self, value: u32) -> u32 { + value + } + #[uniffi::method(default(value = 42.0))] + fn sinon_f32(&self, value: f32) -> f32 { + value + } + #[uniffi::method(default(value = 42.1))] + fn sinon_f64(&self, value: f64) -> f64 { + value + } + // fn sinon_enum(&self, value: Enumeration) -> Enumeration { + // value + // } +} + +#[derive(uniffi::Record)] +pub struct OptionneurDictionnaire { + #[uniffi(default = -8)] + i8_var: i8, + #[uniffi(default = 8)] + u8_var: u8, + #[uniffi(default = -16)] + i16_var: i16, + #[uniffi(default = 0x10)] + u16_var: u16, + #[uniffi(default = -32)] + i32_var: i32, + #[uniffi(default = 32)] + u32_var: u32, + #[uniffi(default = -64)] + i64_var: i64, + #[uniffi(default = 64)] + u64_var: u64, + #[uniffi(default = 4.0)] + float_var: f32, + #[uniffi(default = 8.0)] + double_var: f64, + #[uniffi(default = true)] + boolean_var: bool, + #[uniffi(default = "default")] + string_var: String, + #[uniffi(default = [])] + list_var: Vec, + // enumeration_var: Enumeration, + #[uniffi(default = None)] + dictionnaire_var: Option, +} diff --git a/fixtures/rondpoint-procmacro/tests/bindings/.supported-flavors.txt b/fixtures/rondpoint-procmacro/tests/bindings/.supported-flavors.txt new file mode 100644 index 00000000..c7254cd9 --- /dev/null +++ b/fixtures/rondpoint-procmacro/tests/bindings/.supported-flavors.txt @@ -0,0 +1,2 @@ +wasm +jsi diff --git a/fixtures/rondpoint-procmacro/tests/bindings/test_rondpoint_procmacro.ts b/fixtures/rondpoint-procmacro/tests/bindings/test_rondpoint_procmacro.ts new file mode 100644 index 00000000..97eb5243 --- /dev/null +++ b/fixtures/rondpoint-procmacro/tests/bindings/test_rondpoint_procmacro.ts @@ -0,0 +1,359 @@ +/* + * 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/ + */ + +// fixture=rondpoint +// cargo run --manifest-path ./crates/uniffi-bindgen-react-native/Cargo.toml -- ./fixtures/${fixture}/src/${fixture}.udl --out-dir ./fixtures/${fixture}/generated +// cargo xtask run ./fixtures/${fixture}/tests/bindings/test_${fixture}.ts --cpp ./fixtures/${fixture}/generated/${fixture}.cpp --crate ./fixtures/${fixture} + +import { Asserts, test } from "@/asserts"; +import { + Enumeration, + EnumerationAvecDonnees, + Optionneur, + Dictionnaire, + OptionneurDictionnaire, + Retourneur, + Stringifier, + copieCarte, + copieDictionnaire, + copieEnumeration, + copieEnumerations, + switcheroo, +} from "../../generated/uniffi_rondpointpm"; +import { numberToString } from "@/simulated"; + +test("Round trip a single enum", (t) => { + const input = Enumeration.Deux; + const output = copieEnumeration(input); + t.assertEqual(input, output); +}); + +test("Round trip a list of enum", (t) => { + const input = [Enumeration.Un, Enumeration.Deux]; + const output = copieEnumerations(input); + t.assertEqual(input, output); +}); + +test("Round trip an object literal, without strings", (t) => { + const input: Dictionnaire = Dictionnaire.create({ + un: Enumeration.Deux, + deux: true, + petitNombre: 0, + grosNombre: BigInt("123456789"), + }); + const output = copieDictionnaire(input); + t.assertEqual(input, output); +}); + +test("Round trip a map of strings to enums with values", (t) => { + const input = new Map([ + ["0", new EnumerationAvecDonnees.Zero()], + ["1", new EnumerationAvecDonnees.Un({ premier: 1 })], + ["2", new EnumerationAvecDonnees.Deux({ premier: 2, second: "deux" })], + ]); + const output = copieCarte(input); + t.assertEqual(input, output); +}); + +test("Round trip a boolean", (t) => { + const input = false; + const output = switcheroo(input); + t.assertNotEqual(input, output); +}); + +const affirmAllerRetour = ( + t: Asserts, + fn: (input: T) => T, + fnName: string, + inputs: T[], +) => { + for (const input of inputs) { + const output = fn(input); + t.assertEqual(input, output, `${fnName} roundtrip failing`); + } +}; + +const inputData = { + boolean: [true, false], + i8: [-0x7f, 0, 0x7f], + u8: [0, 0xff], + i16: [-0x7fff, 0, 0x7fff], + u16: [0, 0xffff], + i32: [-0x7fffffff, 0, 0x7fffffff], + u32: [0, 0xffffffff], + i64: [ + -BigInt("0x7fffffffffffffff"), + BigInt("0"), + BigInt("0x7fffffffffffffff"), + ], + u64: [BigInt("0"), BigInt("0xffffffffffffffff")], + f32: [3.5, 27, -113.75, 0.0078125, 0.5, 0, -1], + f64: [Number.MIN_VALUE, Number.MAX_VALUE], + enums: [Enumeration.Un, Enumeration.Deux, Enumeration.Trois], + string: [ + "", + "abc", + "null\u0000byte", + "รฉtรฉ", + "ฺšูŠ ู„ุงุณ ุชู‡ ู„ูˆุณุชู„ูˆ ู„ูˆุณุชู„", + "๐Ÿ˜ปemoji ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆmulti-emoji, ๐Ÿ‡จ๐Ÿ‡ญa flag, a canal, panama", + ], +}; + +// Test the roundtrip across the FFI. +// This shows that the values we send come back in exactly the same state as we sent them. +// i.e. it shows that lowering from kotlin and lifting into rust is symmetrical with +// lowering from rust and lifting into typescript. + +test("Using an object to roundtrip primitives", (t) => { + const rt = new Retourneur(); + t.assertNotNull(rt); + affirmAllerRetour( + t, + rt.identiqueBoolean.bind(rt), + "identiqueBoolean", + inputData.boolean, + ); + + // 8 bit + affirmAllerRetour(t, rt.identiqueI8.bind(rt), "identiqueI8", inputData.i8); + affirmAllerRetour(t, rt.identiqueU8.bind(rt), "identiqueU8", inputData.u8); + + // 16 bit + affirmAllerRetour(t, rt.identiqueI16.bind(rt), "identiqueI16", inputData.i16); + affirmAllerRetour(t, rt.identiqueU16.bind(rt), "identiqueU16", inputData.u16); + + // 32 bits + affirmAllerRetour(t, rt.identiqueI32.bind(rt), "identiqueI32", inputData.i32); + affirmAllerRetour(t, rt.identiqueU32.bind(rt), "identiqueU32", inputData.u32); + affirmAllerRetour( + t, + rt.identiqueFloat.bind(rt), + "identiqueF32", + inputData.f32, + ); + + // 64 bits + affirmAllerRetour(t, rt.identiqueI64.bind(rt), "identiqueI64", inputData.i64); + affirmAllerRetour(t, rt.identiqueU64.bind(rt), "identiqueU64", inputData.u64); + affirmAllerRetour( + t, + rt.identiqueDouble.bind(rt), + "identiqueF32", + inputData.f64, + ); + + rt.uniffiDestroy(); +}); + +test("Testing defaulting properties in record types", (t) => { + const rt = new Retourneur(); + const explicit = OptionneurDictionnaire.create({ + i8Var: -8, + u8Var: 8, + i16Var: -16, + u16Var: 0x10, + i32Var: -32, + u32Var: 32, + i64Var: -BigInt("64"), + u64Var: BigInt("64"), + floatVar: 4.0, + doubleVar: 8.0, + booleanVar: true, + stringVar: "default", + listVar: [], + // enumerationVar: Enumeration.Deux, + dictionnaireVar: undefined, + }); + const defaulted: OptionneurDictionnaire = explicit; //OptionneurDictionnaire.create({}); + t.assertEqual(explicit, defaulted); + + // const actualDefaults = OptionneurDictionnaire.defaults(); + // t.assertEqual(defaulted, actualDefaults); + + affirmAllerRetour( + t, + rt.identiqueOptionneurDictionnaire.bind(rt), + "identiqueOptionneurDictionnaire", + [explicit], + ); + rt.uniffiDestroy(); +}); + +test("Using an object to roundtrip strings", (t) => { + const rt = new Retourneur(); + t.assertNotNull(rt); + affirmAllerRetour( + t, + rt.identiqueString.bind(rt), + "identiqueString", + inputData.string, + ); + rt.uniffiDestroy(); +}); + +// Test one way across the FFI. +// +// We send one representation of a value to lib.rs, and it transforms it into another, a string. +// lib.rs sends the string back, and then we compare here in kotlin. +// +// This shows that the values are transformed into strings the same way in both kotlin and rust. +// i.e. if we assume that the string return works (we test this assumption elsewhere) +// we show that lowering from kotlin and lifting into rust has values that both kotlin and rust +// both stringify in the same way. i.e. the same values. +// +// If we roundtripping proves the symmetry of our lowering/lifting from here to rust, and lowering/lifting from rust t here, +// and this convinces us that lowering/lifting from here to rust is correct, then +// together, we've shown the correctness of the return leg. + +type TypescriptToString = (value: T) => string; +function affirmEnchaine( + t: Asserts, + fn: (input: T) => string, + fnName: string, + inputs: T[], + toString: TypescriptToString = (value: T): string => `${value}`, +) { + for (const input of inputs) { + const expected = toString(input); + const observed = fn(input); + t.assertEqual(expected, observed, `Stringifier ${fnName} failing`); + } +} + +test("Using an object convert into strings", (t) => { + const st = new Stringifier(); + t.assertNotNull(st); + + const input = "JS on Hermes"; + t.assertEqual(`uniffi ๐Ÿ’š ${input}!`, st.wellKnownString(input)); + + affirmEnchaine( + t, + st.toStringBoolean.bind(st), + "toStringBoolean", + inputData.boolean, + ); + affirmEnchaine(t, st.toStringI8.bind(st), "toStringI8", inputData.i8); + affirmEnchaine(t, st.toStringU8.bind(st), "toStringU8", inputData.u8); + affirmEnchaine(t, st.toStringI16.bind(st), "toStringI16", inputData.i16); + affirmEnchaine(t, st.toStringU16.bind(st), "toStringU16", inputData.u16); + affirmEnchaine(t, st.toStringI32.bind(st), "toStringI32", inputData.i32); + affirmEnchaine(t, st.toStringU32.bind(st), "toStringU32", inputData.u32); + affirmEnchaine(t, st.toStringI64.bind(st), "toStringI64", inputData.i64); + affirmEnchaine(t, st.toStringU64.bind(st), "toStringU64", inputData.u64); + affirmEnchaine( + t, + st.toStringFloat.bind(st), + "toStringFloat", + inputData.f32, + numberToString, + ); + affirmEnchaine( + t, + st.toStringDouble.bind(st), + "toStringDouble", + inputData.f64, + numberToString, + ); + + st.uniffiDestroy(); +}); + +test("Default arguments are defaulting", (t) => { + // Prove to ourselves that default arguments are being used. + // Step 1: call the methods without arguments, and check against the UDL. + const op = new Optionneur(); + + t.assertEqual(op.sinonString(), "default"); + t.assertEqual(op.sinonBoolean(), false); + t.assertEqual(op.sinonSequence(), []); + + // optionals + t.assertEqual(op.sinonNull(), null); + t.assertEqual(op.sinonZero(), 0); + + // decimal integers + t.assertEqual(op.sinonI8Dec(), -42); + t.assertEqual(op.sinonU8Dec(), 42); + t.assertEqual(op.sinonI16Dec(), 42); + t.assertEqual(op.sinonU16Dec(), 42); + t.assertEqual(op.sinonI32Dec(), 42); + t.assertEqual(op.sinonU32Dec(), 42); + t.assertEqual(op.sinonI64Dec(), BigInt("42")); + t.assertEqual(op.sinonU64Dec(), BigInt("42")); + + // hexadecimal integers + t.assertEqual(op.sinonI8Hex(), -0x7f); + t.assertEqual(op.sinonU8Hex(), 0xff); + t.assertEqual(op.sinonI16Hex(), 0x7f); + t.assertEqual(op.sinonU16Hex(), 0x7f); + t.assertEqual(op.sinonI32Hex(), 0x7fffffff); + t.assertEqual(op.sinonU32Hex(), 0xffffffff); + t.assertEqual(op.sinonI64Hex(), BigInt("0x7fffffffffffffff")); + t.assertEqual(op.sinonU64Hex(), BigInt("0xffffffffffffffff")); + + // octal integers + t.assertEqual(op.sinonU32Oct(), 493); // 0o755 + + // floats + t.assertEqual(op.sinonF32(), 42.0); + t.assertEqual(op.sinonF64(), 42.1); + + // enums + // t.assertEqual(op.sinonEnum(Enumeration.Trois), Enumeration.Trois); + + op.uniffiDestroy(); +}); + +test("Default arguments are overridden", (t) => { + // Step 2. Convince ourselves that if we pass something else, then that changes the output. + // We have shown something coming out of the sinon methods, but without eyeballing the Rust + // we can't be sure that the arguments will change the return value. + const op = new Optionneur(); + + // Now passing an argument, showing that it wasn't hardcoded anywhere. + affirmAllerRetour( + t, + op.sinonBoolean.bind(op), + "sinonBoolean", + inputData.boolean, + ); + + // 8 bit + affirmAllerRetour(t, op.sinonI8Dec.bind(op), "sinonI8Dec", inputData.i8); + affirmAllerRetour(t, op.sinonI8Hex.bind(op), "sinonI8Hex", inputData.i8); + affirmAllerRetour(t, op.sinonU8Dec.bind(op), "sinonU8Dec", inputData.u8); + affirmAllerRetour(t, op.sinonU8Hex.bind(op), "sinonU8Hex", inputData.u8); + + // 16 bit + affirmAllerRetour(t, op.sinonI16Dec.bind(op), "sinonI16Dec", inputData.i16); + affirmAllerRetour(t, op.sinonI16Hex.bind(op), "sinonI16Hex", inputData.i16); + affirmAllerRetour(t, op.sinonU16Dec.bind(op), "sinonU16Dec", inputData.u16); + affirmAllerRetour(t, op.sinonU16Hex.bind(op), "sinonU16Hex", inputData.u16); + + // 32 bits + affirmAllerRetour(t, op.sinonI32Dec.bind(op), "sinonI32Dec", inputData.i32); + affirmAllerRetour(t, op.sinonI32Hex.bind(op), "sinonI32Hex", inputData.i32); + affirmAllerRetour(t, op.sinonU32Dec.bind(op), "sinonU32Dec", inputData.u32); + affirmAllerRetour(t, op.sinonU32Hex.bind(op), "sinonU32Hex", inputData.u32); + affirmAllerRetour(t, op.sinonU32Oct.bind(op), "sinonU32Oct", inputData.u32); + // 32 bit float + affirmAllerRetour(t, op.sinonF32.bind(op), "sinonF32", inputData.f32); + + // 64 bits + affirmAllerRetour(t, op.sinonI64Dec.bind(op), "sinonI64Dec", inputData.i64); + affirmAllerRetour(t, op.sinonI64Hex.bind(op), "sinonI64Hex", inputData.i64); + affirmAllerRetour(t, op.sinonU64Dec.bind(op), "sinonU64Dec", inputData.u64); + affirmAllerRetour(t, op.sinonU64Hex.bind(op), "sinonU64Hex", inputData.u64); + + // 64 bit float + affirmAllerRetour(t, op.sinonF64.bind(op), "sinonF64", inputData.f64); + + // affirmAllerRetour(t, op.sinonEnum.bind(op), "sinonEnum", inputData.enums); + + op.uniffiDestroy(); +}); diff --git a/fixtures/rondpoint-procmacro/tests/test_generated_bindings.rs b/fixtures/rondpoint-procmacro/tests/test_generated_bindings.rs new file mode 100644 index 00000000..24ffd28d --- /dev/null +++ b/fixtures/rondpoint-procmacro/tests/test_generated_bindings.rs @@ -0,0 +1,5 @@ +/* + * 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/ + */ From ce0a568ce5f50d8ae98053161e47056a13707025 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Wed, 18 Dec 2024 14:00:58 +0000 Subject: [PATCH 4/7] Rename CallStatus to runtime::RustCallStatus --- crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs | 15 +++++++++++---- .../bindings/gen_typescript/templates/wrapper.ts | 2 +- crates/uniffi_wasm/src/lib.rs | 11 ++++++----- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs b/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs index 69ac1042..393ea289 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs @@ -164,13 +164,18 @@ impl<'a> ComponentTemplate<'a> { ident("__module") } + fn uniffi_ident(&self) -> Ident { + ident("__uniffi") + } + fn prelude(&self, ci: &ComponentInterface) -> TokenStream { let runtime_alias_ident = self.runtime_ident(); let runtime_ident = self.flavor.runtime_module(); let namespace_ident = ident(ci.namespace()); let module_ident = self.module_ident(); + let uniffi_alias_ident = self.uniffi_ident(); quote! { - use #runtime_ident::{self as #runtime_alias_ident, IntoRust}; + use #runtime_ident::{self as #runtime_alias_ident, uniffi as #uniffi_alias_ident, IntoRust}; use #namespace_ident as #module_ident; } } @@ -187,6 +192,8 @@ impl<'a> ComponentTemplate<'a> { fn ffi_function(&mut self, func: &FfiFunction) -> TokenStream { let module = self.module_ident(); let runtime = self.runtime_ident(); + let uniffi = self.uniffi_ident(); + let annotation = quote! { #[#runtime::export] }; let func_ident = ident(func.name()); let foreign_func_ident = self.flavor.foreign_ident(func.name()); @@ -227,8 +234,8 @@ impl<'a> ComponentTemplate<'a> { quote! { #annotation - pub fn #foreign_func_ident(#args_decl #foreign_status_ident: &mut #runtime::CallStatus) #decl_suffix { - let mut #rust_status_ident = #runtime::RustCallStatus::default(); + pub fn #foreign_func_ident(#args_decl #foreign_status_ident: &mut #runtime::RustCallStatus) #decl_suffix { + let mut #rust_status_ident = #uniffi::RustCallStatus::default(); #let_value #module::#func_ident(#args_call &mut #rust_status_ident) #call_suffix; #foreign_status_ident.copy_into(#rust_status_ident); #return_value @@ -295,7 +302,7 @@ impl<'a> ComponentTemplate<'a> { FfiType::Callback(_) => quote! { #module::Callback }, FfiType::Struct(_) => quote! { #module::Struct }, FfiType::Handle => quote! { #module::Handle }, - FfiType::RustCallStatus => quote! { #module::RustCallStatus }, + FfiType::RustCallStatus => quote! { #runtime::RustCallStatus }, FfiType::Reference(_ffi_type) => todo!(), FfiType::VoidPointer => quote! { #runtime::VoidPointer }, } diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/wrapper.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/wrapper.ts index 37a06fb7..95886c01 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/wrapper.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/wrapper.ts @@ -48,7 +48,7 @@ const { const uniffiCaller = new UniffiRustCaller(); {%- else %} const nativeModule = () => wasmBundle; -const uniffiCaller = new UniffiRustCaller(() => new wasmBundle.CallStatus()); +const uniffiCaller = new UniffiRustCaller(() => new wasmBundle.RustCallStatus()); {%- endif %} const uniffiIsDebug = diff --git a/crates/uniffi_wasm/src/lib.rs b/crates/uniffi_wasm/src/lib.rs index ba67cc39..aaea6aad 100644 --- a/crates/uniffi_wasm/src/lib.rs +++ b/crates/uniffi_wasm/src/lib.rs @@ -59,23 +59,24 @@ impl IntoRust for uniffi::RustBuffer { #[wasm_bindgen(getter_with_clone)] #[derive(Default)] -pub struct CallStatus { +pub struct RustCallStatus { pub code: i8, pub error_buf: Option, } #[wasm_bindgen] -impl CallStatus { +impl RustCallStatus { #[wasm_bindgen(constructor)] pub fn new() -> Self { Default::default() } } -impl CallStatus { - pub fn copy_into(&mut self, rust: RustCallStatus) { +impl RustCallStatus { + pub fn copy_into(&mut self, rust: uniffi::RustCallStatus) { self.code = rust.code as i8; - self.error_buf = None; + let buf = std::mem::ManuallyDrop::into_inner(rust.error_buf).destroy_into_vec(); + self.error_buf = Some(buf); } } From 9d1b7c2c1aa86947397f9b90b18abef5d2f6d72b Mon Sep 17 00:00:00 2001 From: James Hugman Date: Wed, 18 Dec 2024 14:08:31 +0000 Subject: [PATCH 5/7] Enable Objects for WASM --- .../ubrn_bindgen/src/bindings/extensions.rs | 5 ++++ .../ubrn_bindgen/src/bindings/gen_rust/mod.rs | 13 +++++++-- .../src/bindings/gen_typescript/mod.rs | 4 +++ .../templates/ObjectTemplate.ts | 28 +++++++++++++++++-- crates/uniffi_wasm/src/lib.rs | 3 +- typescript/src/objects.ts | 1 + 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/crates/ubrn_bindgen/src/bindings/extensions.rs b/crates/ubrn_bindgen/src/bindings/extensions.rs index 066fcdb0..58bf1bb3 100644 --- a/crates/ubrn_bindgen/src/bindings/extensions.rs +++ b/crates/ubrn_bindgen/src/bindings/extensions.rs @@ -300,6 +300,11 @@ pub(crate) impl FfiFunction { let name = self.name(); name.contains("ffi__") && name.contains("_internal_") } + + fn is_unsafe(&self) -> bool { + let name = self.name(); + name.contains("_fn_clone_") || name.contains("_fn_free_") || name.contains("_rustbuffer_") + } } #[ext] diff --git a/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs b/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs index 393ea289..9ad66b74 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs @@ -15,7 +15,10 @@ use uniffi_bindgen::{ }; use crate::{ - bindings::{extensions::ComponentInterfaceExt, metadata::ModuleMetadata}, + bindings::{ + extensions::{ComponentInterfaceExt, FfiFunctionExt}, + metadata::ModuleMetadata, + }, switches::SwitchArgs, AbiFlavor, }; @@ -231,10 +234,15 @@ impl<'a> ComponentTemplate<'a> { } else { quote! {} }; + let unsafe_ = if func.is_unsafe() { + quote! { unsafe } + } else { + quote! {} + }; quote! { #annotation - pub fn #foreign_func_ident(#args_decl #foreign_status_ident: &mut #runtime::RustCallStatus) #decl_suffix { + pub #unsafe_ fn #foreign_func_ident(#args_decl #foreign_status_ident: &mut #runtime::RustCallStatus) #decl_suffix { let mut #rust_status_ident = #uniffi::RustCallStatus::default(); #let_value #module::#func_ident(#args_call &mut #rust_status_ident) #call_suffix; #foreign_status_ident.copy_into(#rust_status_ident); @@ -322,6 +330,7 @@ impl<'a> ComponentTemplate<'a> { FfiType::Float32 => quote! { f32 }, FfiType::Float64 => quote! { f64 }, FfiType::RustBuffer(_) => quote! { #uniffi::RustBuffer }, + FfiType::RustArcPtr(_) => quote! { #uniffi::VoidPointer }, _ => todo!(), } } diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs b/crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs index f93b1d80..fe63c6d9 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs @@ -86,6 +86,10 @@ impl TsFlavorParams<'_> { pub(crate) fn supports_text_encoder(&self) -> bool { !matches!(self.inner, &AbiFlavor::Jsi) } + + pub(crate) fn supports_finalization_registry(&self) -> bool { + !matches!(self.inner, &AbiFlavor::Jsi) + } } #[derive(Template)] diff --git a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/ObjectTemplate.ts b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/ObjectTemplate.ts index 2f8a2838..38012848 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/ObjectTemplate.ts +++ b/crates/ubrn_bindgen/src/bindings/gen_typescript/templates/ObjectTemplate.ts @@ -115,10 +115,11 @@ export class {{ impl_class_name }} extends UniffiAbstractObject implements {{ pr * {@inheritDoc uniffi-bindgen-react-native#UniffiAbstractObject.uniffiDestroy} */ uniffiDestroy(): void { - if ((this as any)[destructorGuardSymbol]) { + const ptr = (this as any)[destructorGuardSymbol]; + if (ptr !== undefined) { const pointer = {{ obj_factory }}.pointer(this); {{ obj_factory }}.freePointer(pointer); - this[destructorGuardSymbol].markDestroyed(); + {{ obj_factory }}.unbless(ptr); delete (this as any)[destructorGuardSymbol]; } } @@ -148,6 +149,24 @@ const {{ obj_factory }}: UniffiObjectFactory<{{ type_name }}> = { return instance; }, + {% if flavor.supports_finalization_registry() %} + registry: new FinalizationRegistry((heldValue) => { + {{ obj_factory }}.freePointer(heldValue); + }), + + bless(p: UnsafeMutableRawPointer): UniffiRustArcPtr { + const ptr = { + p, // make sure this object doesn't get optimized away. + markDestroyed: () => undefined, + }; + {{ obj_factory }}.registry.register(ptr, p, ptr); + return ptr; + }, + + unbless(ptr: UniffiRustArcPtr) { + {{ obj_factory }}.registry.unregister(ptr); + }, + {%- else %} bless(p: UnsafeMutableRawPointer): UniffiRustArcPtr { return uniffiCaller.rustCall( /*caller:*/ (status) => @@ -156,6 +175,11 @@ const {{ obj_factory }}: UniffiObjectFactory<{{ type_name }}> = { ); }, + unbless(ptr: UniffiRustArcPtr) { + ptr.markDestroyed(); + }, + {%- endif %} + pointer(obj: {{ type_name }}): UnsafeMutableRawPointer { if ((obj as any)[destructorGuardSymbol] === undefined) { throw new UniffiInternalError.UnexpectedNullPointer(); diff --git a/crates/uniffi_wasm/src/lib.rs b/crates/uniffi_wasm/src/lib.rs index aaea6aad..1fb4a22a 100644 --- a/crates/uniffi_wasm/src/lib.rs +++ b/crates/uniffi_wasm/src/lib.rs @@ -8,6 +8,7 @@ use wasm_bindgen::prelude::*; pub mod uniffi { pub use uniffi::{RustBuffer, RustCallStatus}; + pub type VoidPointer = *const std::ffi::c_void; } pub trait IntoRust { @@ -38,7 +39,7 @@ identity_into_rust!(Int32, i32); identity_into_rust!(Int64, i64); pub type VoidPointer = u64; -impl IntoRust for *const std::ffi::c_void { +impl IntoRust for uniffi::VoidPointer { fn into_rust(v: VoidPointer) -> Self { v as Self } diff --git a/typescript/src/objects.ts b/typescript/src/objects.ts index 0d94d0fe..bd380267 100644 --- a/typescript/src/objects.ts +++ b/typescript/src/objects.ts @@ -56,6 +56,7 @@ export type UnsafeMutableRawPointer = bigint; */ export interface UniffiObjectFactory { bless(pointer: UnsafeMutableRawPointer): UniffiRustArcPtr; + unbless(ptr: UniffiRustArcPtr): void; create(pointer: UnsafeMutableRawPointer): T; pointer(obj: T): UnsafeMutableRawPointer; clonePointer(obj: T): UnsafeMutableRawPointer; From d2e378a4959820cdf7047a1019d90925bfd517e1 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Wed, 18 Dec 2024 14:09:20 +0000 Subject: [PATCH 6/7] Enable Floats and Doubles for WASM --- crates/uniffi_wasm/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/uniffi_wasm/src/lib.rs b/crates/uniffi_wasm/src/lib.rs index 1fb4a22a..1bc7b5f8 100644 --- a/crates/uniffi_wasm/src/lib.rs +++ b/crates/uniffi_wasm/src/lib.rs @@ -37,6 +37,8 @@ identity_into_rust!(Int8, i8); identity_into_rust!(Int16, i16); identity_into_rust!(Int32, i32); identity_into_rust!(Int64, i64); +identity_into_rust!(Float32, f32); +identity_into_rust!(Float64, f64); pub type VoidPointer = u64; impl IntoRust for uniffi::VoidPointer { From 1a7e934d12464eebb43500bb999749e2882d8a53 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Wed, 18 Dec 2024 14:21:45 +0000 Subject: [PATCH 7/7] Tidy generated Rust --- .../ubrn_bindgen/src/bindings/gen_rust/mod.rs | 70 +++++++++---------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs b/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs index 9ad66b74..c117c835 100644 --- a/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs +++ b/crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs @@ -160,15 +160,15 @@ impl<'a> ComponentTemplate<'a> { } fn runtime_ident(&self) -> Ident { - ident("__runtime") + ident("f") } fn module_ident(&self) -> Ident { - ident("__module") + ident("r") } fn uniffi_ident(&self) -> Ident { - ident("__uniffi") + ident("u") } fn prelude(&self, ci: &ComponentInterface) -> TokenStream { @@ -216,9 +216,9 @@ impl<'a> ComponentTemplate<'a> { let needs_call_status = func.has_rust_call_status_arg(); if needs_call_status { - let rust_status_ident = ident("__rust_call_status"); - let foreign_status_ident = ident("__call_status"); - let return_ident = ident("__return"); + let rust_status_ident = ident("u_status_"); + let foreign_status_ident = ident("f_status_"); + let return_ident = ident("value_"); let let_value = if has_return { quote! { let #return_ident = } } else { @@ -425,12 +425,12 @@ mod unit_tests { string.trim(), trim_indent( " - #[__runtime::export] - pub fn ubrn_my_function(__call_status: &mut __runtime::CallStatus) -> __runtime::Int8 { - let mut __rust_call_status = __runtime::RustCallStatus::default(); - let __return = __module::my_function(&mut __rust_call_status).into_js(); - __call_status.copy_into(__rust_call_status); - __return + #[f::export] + pub fn ubrn_my_function(f_status_: &mut f::RustCallStatus) -> f::Int8 { + let mut u_status_ = u::RustCallStatus::default(); + let value_ = r::my_function(&mut u_status_).into_js(); + f_status_.copy_into(u_status_); + value_ } " ) @@ -453,16 +453,12 @@ mod unit_tests { string.trim(), trim_indent( " - #[__runtime::export] - pub fn ubrn_my_function( - num: __runtime::Int32, - __call_status: &mut __runtime::CallStatus, - ) -> __runtime::Int8 { - let mut __rust_call_status = __runtime::RustCallStatus::default(); - let __return = __module::my_function(i32::into_rust(num), &mut __rust_call_status) - .into_js(); - __call_status.copy_into(__rust_call_status); - __return + #[f::export] + pub fn ubrn_my_function(num: f::Int32, f_status_: &mut f::RustCallStatus) -> f::Int8 { + let mut u_status_ = u::RustCallStatus::default(); + let value_ = r::my_function(i32::into_rust(num), &mut u_status_).into_js(); + f_status_.copy_into(u_status_); + value_ } " ) @@ -485,21 +481,21 @@ mod unit_tests { string.trim(), trim_indent( " - #[__runtime::export] + #[f::export] pub fn ubrn_my_function( - left: __runtime::Int32, - right: __runtime::Float32, - __call_status: &mut __runtime::CallStatus, - ) -> __runtime::Int8 { - let mut __rust_call_status = __runtime::RustCallStatus::default(); - let __return = __module::my_function( + left: f::Int32, + right: f::Float32, + f_status_: &mut f::RustCallStatus, + ) -> f::Int8 { + let mut u_status_ = u::RustCallStatus::default(); + let value_ = r::my_function( i32::into_rust(left), f32::into_rust(right), - &mut __rust_call_status, + &mut u_status_, ) .into_js(); - __call_status.copy_into(__rust_call_status); - __return + f_status_.copy_into(u_status_); + value_ }" ) ); @@ -517,11 +513,11 @@ mod unit_tests { string.trim(), trim_indent( " - #[__runtime::export] - pub fn ubrn_my_function(__call_status: &mut __runtime::CallStatus) { - let mut __rust_call_status = __runtime::RustCallStatus::default(); - __module::my_function(&mut __rust_call_status); - __call_status.copy_into(__rust_call_status); + #[f::export] + pub fn ubrn_my_function(f_status_: &mut f::RustCallStatus) { + let mut u_status_ = u::RustCallStatus::default(); + r::my_function(&mut u_status_); + f_status_.copy_into(u_status_); } " )