From 12f237e117b14d04f21dfce248c97f6fe06c9457 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Mon, 17 Nov 2025 01:04:09 +0100 Subject: [PATCH 01/16] fix warnings in testing.rs --- src/testing.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/testing.rs b/src/testing.rs index 2300e06..93515ec 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -17,7 +17,7 @@ pub struct CompileSource { } /// Test execution options -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct TestConfig { /// Custom output directory for test files pub custom_output_dir: Option, @@ -27,16 +27,6 @@ pub struct TestConfig { pub failure_delay_secs: Option, } -impl Default for TestConfig { - fn default() -> Self { - Self { - custom_output_dir: None, - no_delete: false, - failure_delay_secs: None, - } - } -} - impl TestConfig { /// Build from environment variables /// - UNIFFI_DART_TEST_DIR: custom output dir @@ -103,8 +93,10 @@ pub fn run_test_with_output_dir( config_path: Option<&str>, custom_output_dir: Option<&Utf8Path>, ) -> Result<()> { - let mut config = TestConfig::default(); - config.custom_output_dir = custom_output_dir.map(|p| p.to_owned()); + let config = TestConfig { + custom_output_dir: custom_output_dir.map(|p| p.to_owned()), + ..Default::default() + }; run_test_impl(fixture, udl_path, config_path, &config) } @@ -159,7 +151,7 @@ fn run_test_impl( } let mut pubspec = File::create(out_dir.join("pubspec.yaml"))?; - pubspec.write( + pubspec.write_all( b" name: uniffi_test description: testing module for uniffi From 909822a405a8f1489d9108cae220ba2f57415bb6 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Fri, 21 Nov 2025 23:06:17 +0100 Subject: [PATCH 02/16] update to uniffi v0.30.0 --- Cargo.toml | 18 ++++++++---------- fixtures/benchmarks/Cargo.toml | 4 ++-- fixtures/docstring-proc-macro/Cargo.toml | 4 ++-- fixtures/docstring/Cargo.toml | 4 ++-- fixtures/keywords/Cargo.toml | 4 ++-- fixtures/metadata/Cargo.toml | 4 ++-- .../proc-macro-no-implicit-prelude/Cargo.toml | 4 ++-- fixtures/time-types/Cargo.toml | 9 ++++++--- src/gen/code_type.rs | 2 +- src/gen/custom.rs | 2 +- src/gen/enums.rs | 2 +- src/gen/mod.rs | 2 +- src/gen/objects.rs | 15 ++++++++++++--- src/gen/oracle.rs | 3 --- src/gen/primitives/macros.rs | 2 +- src/gen/primitives/mod.rs | 17 ++++++++--------- src/gen/records.rs | 2 +- 17 files changed, 52 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0fd15f2..4a309c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,14 +32,12 @@ required-features = ["binary"] anyhow = "1" paste = "1" heck = "0.5" -uniffi = { workspace = true, features = [ - "build", "tokio" -] } +uniffi = { workspace = true, features = ["build", "tokio"] } uniffi_dart_macro = { path = "./uniffi_dart_macro" } uniffi_bindgen = { workspace = true } camino = "1" serde = "1" -toml = "0.5.1" +toml = "0.9" genco = "0.17.5" proc-macro2 = "1.0.66" @@ -60,10 +58,10 @@ stringcase = "0.4.0" members = [ ".", "uniffi_dart_macro", - + # Working fixtures - only include fixtures that actually work "fixtures/arithmetic", - "fixtures/bytes_types", + "fixtures/bytes_types", "fixtures/duration_type_test", "fixtures/type-limits", "fixtures/hello_world", @@ -77,8 +75,8 @@ members = [ ] [workspace.dependencies] -uniffi = { version = "0.29.0" } -uniffi_bindgen = { version = "0.29.0" } -uniffi_build = { version = "0.29.0" } -uniffi_testing = { version = "0.29.0" } +uniffi = { version = "0.30.0" } +uniffi_bindgen = { version = "0.30.0" } +uniffi_build = { version = "0.30.0" } +uniffi_testing = { version = "0.30.0" } camino = { version = "1.1" } diff --git a/fixtures/benchmarks/Cargo.toml b/fixtures/benchmarks/Cargo.toml index 3372974..b2e118c 100644 --- a/fixtures/benchmarks/Cargo.toml +++ b/fixtures/benchmarks/Cargo.toml @@ -9,10 +9,10 @@ crate-type = ["lib", "cdylib"] bench = false [dependencies] -uniffi = "0.29" +uniffi = "0.30" clap = { version = "4", features = ["cargo", "std", "derive"] } criterion = "0.5.1" [build-dependencies] uniffi-dart = { path = "../../" } -camino = "1" \ No newline at end of file +camino = "1" diff --git a/fixtures/docstring-proc-macro/Cargo.toml b/fixtures/docstring-proc-macro/Cargo.toml index e8bb29f..0098fcf 100644 --- a/fixtures/docstring-proc-macro/Cargo.toml +++ b/fixtures/docstring-proc-macro/Cargo.toml @@ -9,8 +9,8 @@ crate-type = ["lib", "cdylib"] [dependencies] thiserror = "1.0" -uniffi = "0.29" +uniffi = "0.30" [build-dependencies] uniffi-dart = { path = "../../" } -camino = "1" \ No newline at end of file +camino = "1" diff --git a/fixtures/docstring/Cargo.toml b/fixtures/docstring/Cargo.toml index 53ea293..e5d8659 100644 --- a/fixtures/docstring/Cargo.toml +++ b/fixtures/docstring/Cargo.toml @@ -9,9 +9,9 @@ crate-type = ["lib", "cdylib"] [dependencies] thiserror = "1.0" -uniffi = "0.29" +uniffi = "0.30" uniffi-dart = { path = "../../", features = ["bindgen-tests"] } [build-dependencies] uniffi-dart = { path = "../../", features = ["build"] } -camino = { workspace = true } \ No newline at end of file +camino = { workspace = true } diff --git a/fixtures/keywords/Cargo.toml b/fixtures/keywords/Cargo.toml index 03c6b2d..78f4023 100644 --- a/fixtures/keywords/Cargo.toml +++ b/fixtures/keywords/Cargo.toml @@ -8,8 +8,8 @@ name = "keywords" crate-type = ["lib", "cdylib"] [dependencies] -uniffi = "0.29" +uniffi = "0.30" [build-dependencies] uniffi-dart = { path = "../../", features = ["build"] } -camino = { workspace = true } \ No newline at end of file +camino = { workspace = true } diff --git a/fixtures/metadata/Cargo.toml b/fixtures/metadata/Cargo.toml index 1a50f93..18dd66f 100644 --- a/fixtures/metadata/Cargo.toml +++ b/fixtures/metadata/Cargo.toml @@ -9,9 +9,9 @@ crate-type = ["lib", "cdylib"] [dependencies] thiserror = "1.0" -uniffi = "0.29" +uniffi = "0.30" uniffi-dart = { path = "../../", features = ["bindgen-tests"] } [build-dependencies] uniffi-dart = { path = "../../", features = ["build"] } -camino = { workspace = true } \ No newline at end of file +camino = { workspace = true } diff --git a/fixtures/proc-macro-no-implicit-prelude/Cargo.toml b/fixtures/proc-macro-no-implicit-prelude/Cargo.toml index fa6cd7f..0fb5022 100644 --- a/fixtures/proc-macro-no-implicit-prelude/Cargo.toml +++ b/fixtures/proc-macro-no-implicit-prelude/Cargo.toml @@ -13,8 +13,8 @@ myfeature = [] [dependencies] thiserror = "1.0" -uniffi = "0.29" +uniffi = "0.30" [build-dependencies] uniffi-dart = { path = "../../" } -camino = "1" \ No newline at end of file +camino = "1" diff --git a/fixtures/time-types/Cargo.toml b/fixtures/time-types/Cargo.toml index a698e7a..af16cd6 100644 --- a/fixtures/time-types/Cargo.toml +++ b/fixtures/time-types/Cargo.toml @@ -8,10 +8,13 @@ name = "time_types" crate-type = ["lib", "cdylib"] [dependencies] -uniffi = "0.29" +uniffi = "0.30" thiserror = "1.0" -chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] } +chrono = { version = "0.4", default-features = false, features = [ + "alloc", + "std", +] } [build-dependencies] uniffi-dart = { path = "../../", features = ["build"] } -camino = { workspace = true } \ No newline at end of file +camino = { workspace = true } diff --git a/src/gen/code_type.rs b/src/gen/code_type.rs index 73e42b1..dc66277 100644 --- a/src/gen/code_type.rs +++ b/src/gen/code_type.rs @@ -1,5 +1,5 @@ use std::fmt::Debug; -use uniffi_bindgen::backend::Literal; +use uniffi_bindgen::pipeline::general::nodes::Literal; /// A trait tor the implementation. pub trait CodeType: Debug { diff --git a/src/gen/custom.rs b/src/gen/custom.rs index 1cb209a..b37bc44 100644 --- a/src/gen/custom.rs +++ b/src/gen/custom.rs @@ -2,8 +2,8 @@ use super::oracle::{AsCodeType, DartCodeOracle}; use super::render::{Renderable, TypeHelperRenderer}; use super::CodeType; use genco::prelude::*; -use uniffi_bindgen::backend::Type; use uniffi_bindgen::interface::AsType; +use uniffi_bindgen::interface::Type; #[derive(Debug)] pub struct CustomCodeType { diff --git a/src/gen/enums.rs b/src/gen/enums.rs index bdeb098..adbbf19 100644 --- a/src/gen/enums.rs +++ b/src/gen/enums.rs @@ -1,8 +1,8 @@ use crate::gen::CodeType; use genco::prelude::*; use heck::ToLowerCamelCase; -use uniffi_bindgen::backend::Literal; use uniffi_bindgen::interface::{AsType, Enum, Field, Type}; +use uniffi_bindgen::pipeline::general::nodes::Literal; use super::oracle::{AsCodeType, DartCodeOracle}; use super::render::{AsRenderable, Renderable, TypeHelperRenderer}; diff --git a/src/gen/mod.rs b/src/gen/mod.rs index 4f8da92..2c93ed9 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -246,7 +246,7 @@ impl BindingGenerator for DartBindingGenerator { Ok(()) } - fn new_config(&self, root_toml: &toml::value::Value) -> Result { + fn new_config(&self, root_toml: &toml::Value) -> Result { Ok( match root_toml.get("bindings").and_then(|b| b.get("dart")) { Some(v) => v.clone().try_into()?, diff --git a/src/gen/objects.rs b/src/gen/objects.rs index 27a66f5..eb9df63 100644 --- a/src/gen/objects.rs +++ b/src/gen/objects.rs @@ -8,8 +8,8 @@ use crate::gen::callback_interface::{ use crate::gen::CodeType; use heck::ToLowerCamelCase; use std::string::ToString; -use uniffi_bindgen::backend::Literal; use uniffi_bindgen::interface::{AsType, Method, Object, ObjectImpl, UniffiTrait}; +use uniffi_bindgen::pipeline::general::nodes::Literal; use crate::gen::oracle::{AsCodeType, DartCodeOracle}; use crate::gen::render::AsRenderable; @@ -190,8 +190,13 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da } for trait_impl in obj.trait_impls() { - let trait_iface = - DartCodeOracle::trait_interface_name(type_helper.get_ci(), &trait_impl.trait_name); + // Extract the trait name from the trait_ty Type + let trait_name = match &trait_impl.trait_ty { + uniffi_bindgen::interface::Type::Object { name, .. } => name, + uniffi_bindgen::interface::Type::CallbackInterface { name, .. } => name, + _ => continue, // Skip if it's not an Object or CallbackInterface + }; + let trait_iface = DartCodeOracle::trait_interface_name(type_helper.get_ci(), trait_name); if !implements.contains(&trait_iface) { implements.push(trait_iface); } @@ -420,6 +425,10 @@ fn generate_trait_helpers(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> }); generated_hash = true; } + UniffiTrait::Ord { .. } => { + // Ord trait is not currently supported in Dart bindings + // Skip generation for now + } } } diff --git a/src/gen/oracle.rs b/src/gen/oracle.rs index 5baed9b..d642404 100644 --- a/src/gen/oracle.rs +++ b/src/gen/oracle.rs @@ -152,7 +152,6 @@ impl DartCodeOracle { FfiType::UInt64 => quote!(int), FfiType::Float32 => quote!(double), FfiType::Float64 => quote!(double), - FfiType::RustArcPtr(_) => quote!(Pointer), FfiType::RustBuffer(ext) => Self::rust_buffer_name(ext, ci), FfiType::ForeignBytes => quote!(ForeignBytes), FfiType::Handle => quote!(Pointer), @@ -181,7 +180,6 @@ impl DartCodeOracle { FfiType::UInt64 => quote!(Uint64), FfiType::Float32 => quote!(Float), FfiType::Float64 => quote!(Double), - FfiType::RustArcPtr(_) => quote!(Pointer), FfiType::RustBuffer(ext) => Self::rust_buffer_name(ext, ci), FfiType::ForeignBytes => quote!(ForeignBytes), FfiType::Handle => quote!(Pointer), @@ -206,7 +204,6 @@ impl DartCodeOracle { FfiType::UInt64 => quote!(Uint64), FfiType::Float32 => quote!(Float), FfiType::Float64 => quote!(Double), - FfiType::RustArcPtr(_) => quote!(Pointer), FfiType::RustBuffer(_) => quote!(RustBuffer), FfiType::Callback(name) => quote!(Pointer<$(Self::ffi_callback_name(name))>), FfiType::Struct(name) => quote!(Pointer<$(Self::ffi_struct_name(name))>), diff --git a/src/gen/primitives/macros.rs b/src/gen/primitives/macros.rs index 96ccf01..9dbceaf 100644 --- a/src/gen/primitives/macros.rs +++ b/src/gen/primitives/macros.rs @@ -9,7 +9,7 @@ macro_rules! impl_code_type_for_primitive { $class_name.into() } - fn literal(&self, literal: &uniffi_bindgen::backend::Literal) -> String { + fn literal(&self, literal: &uniffi_bindgen::pipeline::general::nodes::Literal) -> String { $crate::gen::primitives::render_literal(&literal) } diff --git a/src/gen/primitives/mod.rs b/src/gen/primitives/mod.rs index 109cdd4..4c7056a 100644 --- a/src/gen/primitives/mod.rs +++ b/src/gen/primitives/mod.rs @@ -8,16 +8,15 @@ use crate::gen::render::{Renderable, TypeHelperRenderer}; use crate::gen::CodeType; use genco::prelude::*; use paste::paste; -use uniffi_bindgen::backend::Literal; -use uniffi_bindgen::interface::{Radix, Type}; +use uniffi_bindgen::pipeline::general::nodes::{Literal, Radix, Type, TypeNode}; pub use boolean::BooleanCodeType; pub use duration::DurationCodeType; pub use string::StringCodeType; fn render_literal(literal: &Literal) -> String { - fn typed_number(type_: &Type, num_str: String) -> String { - match type_ { + fn typed_number(type_node: &TypeNode, num_str: String) -> String { + match &type_node.ty { Type::Int8 | Type::UInt8 | Type::Int16 @@ -35,23 +34,23 @@ fn render_literal(literal: &Literal) -> String { match literal { Literal::Boolean(v) => format!("{v}"), Literal::String(s) => format!("'{s}'"), - Literal::Int(i, radix, type_) => typed_number( - type_, + Literal::Int(i, radix, type_node) => typed_number( + type_node, match radix { Radix::Octal => format!("{i:#x}"), Radix::Decimal => format!("{i}"), Radix::Hexadecimal => format!("{i:#x}"), }, ), - Literal::UInt(i, radix, type_) => typed_number( - type_, + Literal::UInt(i, radix, type_node) => typed_number( + type_node, match radix { Radix::Octal => format!("{i:#x}"), Radix::Decimal => format!("{i}"), Radix::Hexadecimal => format!("{i:#x}"), }, ), - Literal::Float(string, type_) => typed_number(type_, string.clone()), + Literal::Float(string, type_node) => typed_number(type_node, string.clone()), _ => unreachable!("Literal"), } } diff --git a/src/gen/records.rs b/src/gen/records.rs index f460fe6..e448a81 100644 --- a/src/gen/records.rs +++ b/src/gen/records.rs @@ -3,8 +3,8 @@ use super::render::{Renderable, TypeHelperRenderer}; use super::types::generate_type; use crate::gen::CodeType; use genco::prelude::*; -use uniffi_bindgen::backend::Literal; use uniffi_bindgen::interface::{AsType, Record}; +use uniffi_bindgen::pipeline::general::nodes::Literal; #[derive(Debug)] pub struct RecordCodeType { From 0306d72ca876879f337607cdea7085622c581492 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Sat, 22 Nov 2025 00:57:35 +0100 Subject: [PATCH 03/16] wrap the lifter with a conversion from int to Pointer --- src/gen/objects.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/gen/objects.rs b/src/gen/objects.rs index eb9df63..fc1ef8c 100644 --- a/src/gen/objects.rs +++ b/src/gen/objects.rs @@ -320,6 +320,18 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d }; if func.is_async() { + // For async methods returning objects, we need to convert the int pointer to Pointer + let async_lifter = if let Some(ret_type) = func.return_type() { + match ret_type { + uniffi_bindgen::interface::Type::Object { .. } => { + quote!((ptr) => $lifter(Pointer.fromAddress(ptr))) + } + _ => lifter.clone() + } + } else { + lifter.clone() + }; + quote!( Future<$ret> $(DartCodeOracle::fn_name(func.name()))($args) { return uniffiRustCallAsync( @@ -330,7 +342,7 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d $(DartCodeOracle::async_poll(func, type_helper.get_ci())), $(DartCodeOracle::async_complete(func, type_helper.get_ci())), $(DartCodeOracle::async_free(func, type_helper.get_ci())), - $lifter, + $async_lifter, $error_handler, ); } From 2965becead310aa48d685bb384e87746bb501553 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Sat, 22 Nov 2025 13:30:38 +0100 Subject: [PATCH 04/16] pass fmt check --- src/gen/objects.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gen/objects.rs b/src/gen/objects.rs index fc1ef8c..04c5147 100644 --- a/src/gen/objects.rs +++ b/src/gen/objects.rs @@ -326,7 +326,7 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d uniffi_bindgen::interface::Type::Object { .. } => { quote!((ptr) => $lifter(Pointer.fromAddress(ptr))) } - _ => lifter.clone() + _ => lifter.clone(), } } else { lifter.clone() From d55324b2314eb33da07eb84eabaa13dc5d79b9cc Mon Sep 17 00:00:00 2001 From: kumulynja Date: Sun, 23 Nov 2025 15:47:55 +0100 Subject: [PATCH 05/16] Remove Directory.current.path from library loading for Native Assets compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the generated Dart code to use simple library names instead of absolute paths for Android and Linux platforms. This makes the generated code compatible with Dart's Native Assets system. Changes: - Android: "${Directory.current.path}/lib{libname}.so" → "lib{libname}.so" - Linux: "${Directory.current.path}/lib{libname}.so" → "lib{libname}.so" --- src/gen/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gen/mod.rs b/src/gen/mod.rs index 2c93ed9..a6d4da4 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -161,9 +161,9 @@ impl<'a> DartWrapper<'a> { static final DynamicLibrary _dylib = _open(); static DynamicLibrary _open() { - if (Platform.isAndroid) return DynamicLibrary.open($(format!("\"${{Directory.current.path}}/lib{libname}.so\""))); + if (Platform.isAndroid) return DynamicLibrary.open($(format!("\"lib{libname}.so\""))); if (Platform.isIOS) return DynamicLibrary.executable(); - if (Platform.isLinux) return DynamicLibrary.open($(format!("\"${{Directory.current.path}}/lib{libname}.so\""))); + if (Platform.isLinux) return DynamicLibrary.open($(format!("\"lib{libname}.so\""))); if (Platform.isMacOS) return DynamicLibrary.open($(format!("\"lib{libname}.dylib\""))); if (Platform.isWindows) return DynamicLibrary.open($(format!("\"{libname}.dll\""))); throw UnsupportedError("Unsupported platform: ${Platform.operatingSystem}"); From 930f177d3ffc3626b7a14d6c7b17db4da938b3f2 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Sun, 23 Nov 2025 19:18:14 +0100 Subject: [PATCH 06/16] Add configurable library loading strategy for Native Assets support Introduces a `library_loading_strategy` configuration option that allows users to choose between traditional directory-based library loading and Native Assets mode. Configuration options: - `directory_path` (default): Uses `${Directory.current.path}` for Android/Linux, maintaining backward compatibility - `native_assets`: Uses `DynamicLibrary.process()` for all platforms, enabling Dart Native Assets integration Usage in uniffi.toml: ```toml [bindings.dart] library_loading_strategy = "native_assets" --- src/gen/mod.rs | 53 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/gen/mod.rs b/src/gen/mod.rs index a6d4da4..8c8ef1f 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -33,12 +33,27 @@ mod types; pub use code_type::CodeType; +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum LibraryLoadingStrategy { + DirectoryPath, + NativeAssets, +} + +impl Default for LibraryLoadingStrategy { + fn default() -> Self { + LibraryLoadingStrategy::DirectoryPath + } +} + #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Config { package_name: Option, cdylib_name: Option, #[serde(default)] external_packages: HashMap, + #[serde(default)] + library_loading_strategy: LibraryLoadingStrategy, } impl From<&ComponentInterface> for Config { @@ -47,6 +62,7 @@ impl From<&ComponentInterface> for Config { package_name: Some(ci.namespace().to_owned()), cdylib_name: Some(ci.namespace().to_owned()), external_packages: HashMap::new(), + library_loading_strategy: LibraryLoadingStrategy::default(), } } } @@ -67,6 +83,10 @@ impl Config { "uniffi".into() } } + + pub fn library_loading_strategy(&self) -> &LibraryLoadingStrategy { + &self.library_loading_strategy + } } pub struct DartWrapper<'a> { @@ -88,6 +108,7 @@ impl<'a> DartWrapper<'a> { fn generate(&self) -> dart::Tokens { let package_name = self.config.package_name(); let libname = self.config.cdylib_name(); + let loading_strategy = self.config.library_loading_strategy(); let (type_helper_code, functions_definitions) = &self.type_renderer.render(); @@ -148,6 +169,29 @@ impl<'a> DartWrapper<'a> { definitions } + let open_method = match loading_strategy { + LibraryLoadingStrategy::DirectoryPath => quote! { + static DynamicLibrary _open() { + if (Platform.isAndroid) return DynamicLibrary.open($(format!("\"${{Directory.current.path}}/lib{libname}.so\""))); + if (Platform.isIOS) return DynamicLibrary.executable(); + if (Platform.isLinux) return DynamicLibrary.open($(format!("\"${{Directory.current.path}}/lib{libname}.so\""))); + if (Platform.isMacOS) return DynamicLibrary.open($(format!("\"lib{libname}.dylib\""))); + if (Platform.isWindows) return DynamicLibrary.open($(format!("\"{libname}.dll\""))); + throw UnsupportedError("Unsupported platform: ${Platform.operatingSystem}"); + } + }, + LibraryLoadingStrategy::NativeAssets => quote! { + static DynamicLibrary _open() { + if (Platform.isAndroid) return DynamicLibrary.process(); + if (Platform.isIOS) return DynamicLibrary.process(); + if (Platform.isLinux) return DynamicLibrary.process(); + if (Platform.isMacOS) return DynamicLibrary.process(); + if (Platform.isWindows) return DynamicLibrary.process(); + throw UnsupportedError("Unsupported platform: ${Platform.operatingSystem}"); + } + }, + }; + quote! { library $package_name; @@ -160,14 +204,7 @@ impl<'a> DartWrapper<'a> { static final DynamicLibrary _dylib = _open(); - static DynamicLibrary _open() { - if (Platform.isAndroid) return DynamicLibrary.open($(format!("\"lib{libname}.so\""))); - if (Platform.isIOS) return DynamicLibrary.executable(); - if (Platform.isLinux) return DynamicLibrary.open($(format!("\"lib{libname}.so\""))); - if (Platform.isMacOS) return DynamicLibrary.open($(format!("\"lib{libname}.dylib\""))); - if (Platform.isWindows) return DynamicLibrary.open($(format!("\"{libname}.dll\""))); - throw UnsupportedError("Unsupported platform: ${Platform.operatingSystem}"); - } + $open_method static final _UniffiLib instance = _UniffiLib._(); From 220bbbe5a490333c80c2ab042722e5e28f939d2b Mon Sep 17 00:00:00 2001 From: kumulynja Date: Sun, 23 Nov 2025 19:43:18 +0100 Subject: [PATCH 07/16] Fix config_file_override being ignored in library mode The generate_dart_bindings function was ignoring the config_file_override parameter when library_mode was true, always using the udl_file path instead. This prevented custom configurations in uniffi.toml from being read when generating bindings in library mode. Fixed by using config_file_override when provided, falling back to udl_file only when no override is specified: let config_path = config_file_override.unwrap_or(udl_file) This ensures the library_loading_strategy and other custom configurations are properly applied in library mode. --- src/gen/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gen/mod.rs b/src/gen/mod.rs index 8c8ef1f..1597b86 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -328,11 +328,13 @@ pub fn generate_dart_bindings( library_mode: bool, ) -> anyhow::Result<()> { if library_mode { + // Use config_file_override if provided, otherwise use udl_file + let config_path = config_file_override.unwrap_or(udl_file).to_string(); uniffi_bindgen::library_mode::generate_bindings( library_file, None, &DartBindingGenerator {}, - &LocalConfigSupplier(udl_file.to_string()), + &LocalConfigSupplier(config_path), None, out_dir_override.unwrap(), true, From 790e3514eed75a73e44fe1c4a59e7950553f41b9 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Mon, 24 Nov 2025 01:43:05 +0100 Subject: [PATCH 08/16] Migrate to Dart Native Assets with @Native annotations This is a major refactor that modernizes the FFI bindings to use Dart 3.10+'s Native Assets system instead of manual DynamicLibrary.open() calls. Key changes: - Replace DynamicLibrary.open() with @Native annotations using asset IDs - Generate Native Assets build hooks (hook/build.dart) using hooks package - Implement asset ID system: package:{dart_package_name}/uniffi:{cdylib_name} - Update test infrastructure to support Native Assets in test environments - Add code_assets and hooks as dependencies in generated pubspec.yaml - Remove runtime library path resolution (handled by Native Assets) Technical details: - Asset IDs use format: "package:{package}/uniffi:{cdylib}" - Build hooks register simple names; Dart auto-prefixes with package path - Generated Dart code constructs full asset ID for @Native annotations - Preserved library_file parameter (needed for proc macro metadata extraction) Benefits: - No manual library loading required - Cross-platform library resolution handled automatically - Better integration with Dart's build system - Simpler generated code with declarative @Native functions All 19 tests passing with new Native Assets implementation. --- src/gen/callback_interface.rs | 2 +- src/gen/functions.rs | 6 +- src/gen/mod.rs | 205 +++++++++++++++++----------------- src/gen/objects.rs | 29 +++-- src/gen/oracle.rs | 13 +-- src/gen/types.rs | 8 +- src/testing.rs | 107 +++++++++++++++--- 7 files changed, 227 insertions(+), 143 deletions(-) diff --git a/src/gen/callback_interface.rs b/src/gen/callback_interface.rs index 136f3d5..7baad3f 100644 --- a/src/gen/callback_interface.rs +++ b/src/gen/callback_interface.rs @@ -330,7 +330,7 @@ pub fn generate_callback_interface_vtable_init_function( $(&vtable_static_instance_name).ref.uniffiFree = $(format!("{}FreePointer", DartCodeOracle::fn_name(callback_name))); rustCall((status) { - _UniffiLib.instance.uniffi_$(ffi_module)_fn_init_callback_vtable_$(snake_callback)( + uniffi_$(ffi_module)_fn_init_callback_vtable_$(snake_callback)( $(vtable_static_instance_name), ); checkCallStatus(NullRustCallStatusErrorHandler(), status); diff --git a/src/gen/functions.rs b/src/gen/functions.rs index d771dea..13834f0 100644 --- a/src/gen/functions.rs +++ b/src/gen/functions.rs @@ -35,7 +35,7 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) quote!( Future<$ret> $(DartCodeOracle::fn_name(func.name()))($args) { return uniffiRustCallAsync( - () => $(DartCodeOracle::find_lib_instance()).$(func.ffi_func().name())( + () => $(func.ffi_func().name())( $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) ), $(DartCodeOracle::async_poll(func, type_helper.get_ci())), @@ -50,7 +50,7 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) quote!( $ret $(DartCodeOracle::fn_name(func.name()))($args) { return rustCall((status) { - $(DartCodeOracle::find_lib_instance()).$(func.ffi_func().name())( + $(func.ffi_func().name())( $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status ); }, $error_handler); @@ -59,7 +59,7 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) } else { quote!( $ret $(DartCodeOracle::fn_name(func.name()))($args) { - return rustCall((status) => $lifter($(DartCodeOracle::find_lib_instance()).$(func.ffi_func().name())( + return rustCall((status) => $lifter($(func.ffi_func().name())( $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status )), $error_handler); } diff --git a/src/gen/mod.rs b/src/gen/mod.rs index 1597b86..95c0224 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -33,27 +33,13 @@ mod types; pub use code_type::CodeType; -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum LibraryLoadingStrategy { - DirectoryPath, - NativeAssets, -} - -impl Default for LibraryLoadingStrategy { - fn default() -> Self { - LibraryLoadingStrategy::DirectoryPath - } -} - #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Config { package_name: Option, cdylib_name: Option, #[serde(default)] external_packages: HashMap, - #[serde(default)] - library_loading_strategy: LibraryLoadingStrategy, + asset_id: Option, } impl From<&ComponentInterface> for Config { @@ -62,7 +48,7 @@ impl From<&ComponentInterface> for Config { package_name: Some(ci.namespace().to_owned()), cdylib_name: Some(ci.namespace().to_owned()), external_packages: HashMap::new(), - library_loading_strategy: LibraryLoadingStrategy::default(), + asset_id: None, } } } @@ -84,8 +70,15 @@ impl Config { } } - pub fn library_loading_strategy(&self) -> &LibraryLoadingStrategy { - &self.library_loading_strategy + pub fn asset_id(&self) -> String { + if let Some(asset_id) = &self.asset_id { + asset_id.clone() + } else { + // Default: uniffi:{cdylib_name} + // Dart's Native Assets system automatically prefixes this with package:{dart_package_name}/ + // so the full ID becomes package:{dart_package_name}/uniffi:{cdylib_name} + format!("uniffi:{}", self.cdylib_name()) + } } } @@ -106,13 +99,12 @@ impl<'a> DartWrapper<'a> { } fn generate(&self) -> dart::Tokens { - let package_name = self.config.package_name(); - let libname = self.config.cdylib_name(); - let loading_strategy = self.config.library_loading_strategy(); + let package_name = &self.config.package_name(); let (type_helper_code, functions_definitions) = &self.type_renderer.render(); - fn uniffi_function_definitions(ci: &ComponentInterface) -> dart::Tokens { + // Generate @Native external function definitions + fn uniffi_function_definitions(ci: &ComponentInterface, asset_id: &str) -> dart::Tokens { let mut definitions = quote!(); let mut defined_functions = HashSet::new(); // Track defined function names @@ -125,115 +117,98 @@ impl<'a> DartWrapper<'a> { continue; } - let (native_return_type, dart_return_type) = match fun.return_type() { - Some(return_type) => ( - quote! { $(DartCodeOracle::ffi_native_type_label(Some(return_type), ci)) }, - quote! { $(DartCodeOracle::ffi_dart_type_label(Some(return_type), ci)) }, - ), - None => (quote! { Void }, quote! { void }), + // For @Native, we need both native types (for the annotation) and Dart types (for the external declaration) + let native_return_type = match fun.return_type() { + Some(return_type) => { + quote! { $(DartCodeOracle::ffi_native_type_label(Some(return_type), ci)) } + } + None => quote! { Void }, + }; + + let dart_return_type = match fun.return_type() { + Some(return_type) => { + quote! { $(DartCodeOracle::ffi_dart_type_label(Some(return_type), ci)) } + } + None => quote! { void }, }; let (native_args, dart_args) = { - let mut native_args = quote!(); - let mut dart_args = quote!(); + let mut native_arg_vec = vec![]; + let mut dart_arg_with_names_vec = vec![]; for arg in fun.arguments() { - native_args.append( - quote!($(DartCodeOracle::ffi_native_type_label(Some(&arg.type_()), ci)),), - ); - dart_args.append( - quote!($(DartCodeOracle::ffi_dart_type_label(Some(&arg.type_()), ci)),), - ); + let arg_name = arg.name(); + let native_type = DartCodeOracle::ffi_native_type_label(Some(&arg.type_()), ci); + let dart_type = DartCodeOracle::ffi_dart_type_label(Some(&arg.type_()), ci); + + native_arg_vec.push(native_type); + dart_arg_with_names_vec.push(quote!($dart_type $arg_name)); } if fun.has_rust_call_status_arg() { - native_args.append(quote!(Pointer)); - dart_args.append(quote!(Pointer)); + native_arg_vec.push(quote!(Pointer)); + dart_arg_with_names_vec.push(quote!(Pointer uniffiStatus)); } + let native_args = quote!($(for (i, arg) in native_arg_vec.iter().enumerate() => $(if i > 0 => , )$[' ']$arg)); + let dart_args = quote!($(for (i, arg) in dart_arg_with_names_vec.iter().enumerate() => $(if i > 0 => , )$[' ']$arg)); (native_args, dart_args) }; - let lookup_fn = quote! { - _dylib.lookupFunction< - $native_return_type Function($(&native_args)), - $(&dart_return_type) Function($(&dart_args)) - >($(format!("\"{fun_name}\""))) - }; - + // Generate @Native annotation with assetId + // @Native uses the function name as symbol automatically + // assetId references the _uniffiAssetId constant definitions.append(quote! { - late final $dart_return_type Function($dart_args) $fun_name = $lookup_fn; + @Native<$(&native_return_type) Function($(&native_args))>( + assetId: $asset_id + ) + external $(&dart_return_type) $fun_name($(&dart_args)); + $['\n'] }); } definitions } - let open_method = match loading_strategy { - LibraryLoadingStrategy::DirectoryPath => quote! { - static DynamicLibrary _open() { - if (Platform.isAndroid) return DynamicLibrary.open($(format!("\"${{Directory.current.path}}/lib{libname}.so\""))); - if (Platform.isIOS) return DynamicLibrary.executable(); - if (Platform.isLinux) return DynamicLibrary.open($(format!("\"${{Directory.current.path}}/lib{libname}.so\""))); - if (Platform.isMacOS) return DynamicLibrary.open($(format!("\"lib{libname}.dylib\""))); - if (Platform.isWindows) return DynamicLibrary.open($(format!("\"{libname}.dll\""))); - throw UnsupportedError("Unsupported platform: ${Platform.operatingSystem}"); - } - }, - LibraryLoadingStrategy::NativeAssets => quote! { - static DynamicLibrary _open() { - if (Platform.isAndroid) return DynamicLibrary.process(); - if (Platform.isIOS) return DynamicLibrary.process(); - if (Platform.isLinux) return DynamicLibrary.process(); - if (Platform.isMacOS) return DynamicLibrary.process(); - if (Platform.isWindows) return DynamicLibrary.process(); - throw UnsupportedError("Unsupported platform: ${Platform.operatingSystem}"); - } - }, - }; + let asset_id_suffix = &self.config.asset_id(); // e.g., "uniffi:hello_world" quote! { library $package_name; $(type_helper_code) // Imports, Types and Type Helper - $(functions_definitions) - - class _UniffiLib { - _UniffiLib._(); + // Generated by uniffi-dart – do NOT edit. + // This asset ID is used by @Native annotations to locate the native library + // via Native Assets. Dart automatically prefixes asset names with "package:{packageName}/", + // so we construct the full ID here to match what the build hook registers. + // The asset ID format is: package:{dart_package_name}/uniffi:{cdylib_name} + const _uniffiAssetId = $(quoted(format!("package:{}/{}", package_name, asset_id_suffix))); - static final DynamicLibrary _dylib = _open(); - - $open_method - - static final _UniffiLib instance = _UniffiLib._(); + $(functions_definitions) - $(uniffi_function_definitions(self.ci)) + // FFI function definitions using @Native + $(uniffi_function_definitions(self.ci, "_uniffiAssetId")) - static void _checkApiVersion() { - final bindingsVersion = $(self.ci.uniffi_contract_version()); - final scaffoldingVersion = _UniffiLib.instance.$(self.ci.ffi_uniffi_contract_version().name())(); - if (bindingsVersion != scaffoldingVersion) { - throw UniffiInternalError.panicked("UniFFI contract version mismatch: bindings version $bindingsVersion, scaffolding version $scaffoldingVersion"); - } - } - - static void _checkApiChecksums() { - $(for (name, expected_checksum) in self.ci.iter_checksums() => - if (_UniffiLib.instance.$(name)() != $expected_checksum) { - throw UniffiInternalError.panicked("UniFFI API checksum mismatch"); - } - ) + // API version and checksum validation + void _checkApiVersion() { + final bindingsVersion = $(self.ci.uniffi_contract_version()); + final scaffoldingVersion = $(self.ci.ffi_uniffi_contract_version().name())(); + if (bindingsVersion != scaffoldingVersion) { + throw UniffiInternalError.panicked("UniFFI contract version mismatch: bindings version $bindingsVersion, scaffolding version $scaffoldingVersion"); } } - void initialize() { - _UniffiLib._open(); + void _checkApiChecksums() { + $(for (name, expected_checksum) in self.ci.iter_checksums() => + if ($(name)() != $expected_checksum) { + throw UniffiInternalError.panicked("UniFFI API checksum mismatch"); + } + ) } void ensureInitialized() { - _UniffiLib._checkApiVersion(); - _UniffiLib._checkApiChecksums(); + _checkApiVersion(); + _checkApiChecksums(); } } } @@ -320,6 +295,27 @@ impl BindgenCrateConfigSupplier for LocalConfigSupplier { } } +pub struct ConfigFileSupplier(String); +impl BindgenCrateConfigSupplier for ConfigFileSupplier { + fn get_udl(&self, _crate_name: &str, _udl_name: &str) -> Result { + // We don't have UDL in library mode, return empty + Ok(String::new()) + } + + fn get_toml(&self, _crate_name: &str) -> Result> { + let file = std::fs::File::open(self.0.clone())?; + let mut reader = std::io::BufReader::new(file); + let mut content = String::new(); + reader.read_to_string(&mut content)?; + let toml_value: toml::Value = toml::from_str(&content)?; + if let toml::Value::Table(table) = toml_value { + Ok(Some(table)) + } else { + Ok(None) + } + } +} + pub fn generate_dart_bindings( udl_file: &Utf8Path, config_file_override: Option<&Utf8Path>, @@ -328,19 +324,28 @@ pub fn generate_dart_bindings( library_mode: bool, ) -> anyhow::Result<()> { if library_mode { - // Use config_file_override if provided, otherwise use udl_file - let config_path = config_file_override.unwrap_or(udl_file).to_string(); + // In library mode, we need to read and parse the config file ourselves + // because library_mode::generate_bindings gets config from library metadata + let config_supplier: Box = + if let Some(config_path) = config_file_override { + Box::new(ConfigFileSupplier(config_path.to_string())) + } else { + Box::new(LocalConfigSupplier(udl_file.to_string())) + }; + uniffi_bindgen::library_mode::generate_bindings( library_file, - None, + None, // crate name filter &DartBindingGenerator {}, - &LocalConfigSupplier(config_path), + config_supplier.as_ref(), None, out_dir_override.unwrap(), true, )?; Ok(()) } else { + // Note: library_file is needed by uniffi_bindgen to extract metadata from proc macros, + // even though we don't use it for DynamicLibrary.open() anymore (Native Assets handle that) uniffi_bindgen::generate_external_bindings( &DartBindingGenerator {}, udl_file, diff --git a/src/gen/objects.rs b/src/gen/objects.rs index 04c5147..6c73003 100644 --- a/src/gen/objects.rs +++ b/src/gen/objects.rs @@ -102,7 +102,6 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da let interface_name = DartCodeOracle::object_interface_name(type_helper.get_ci(), obj); let interface_definition = generate_object_interface(obj, &interface_name, type_helper); let finalizer_cls_name = &format!("{cls_name}Finalizer"); - let lib_instance = &DartCodeOracle::find_lib_instance(); let ffi_object_free_name = obj.ffi_object_free().name(); let ffi_object_clone_name = obj.ffi_object_clone().name(); @@ -150,7 +149,7 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da quote! { // Public constructor $dart_constructor_decl($dart_params) : _ptr = rustCall((status) => - $lib_instance.$ffi_func_name( + $ffi_func_name( $ffi_call_args status ), $error_handler @@ -234,7 +233,7 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da $interface_definition final _$finalizer_cls_name = Finalizer>((ptr) { - rustCall((status) => $lib_instance.$ffi_object_free_name(ptr, status)); + rustCall((status) => $ffi_object_free_name(ptr, status)); }); class $cls_name $implements_clause { @@ -258,7 +257,7 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da } Pointer uniffiClonePointer() { - return rustCall((status) => $lib_instance.$ffi_object_clone_name(_ptr, status)); + return rustCall((status) => $ffi_object_clone_name(_ptr, status)); } // A Rust pointer is 8 bytes @@ -280,7 +279,7 @@ pub fn generate_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> da void dispose() { _$finalizer_cls_name.detach(this); - rustCall((status) => $lib_instance.$ffi_object_free_name(_ptr, status)); + rustCall((status) => $ffi_object_free_name(_ptr, status)); } $to_string_method @@ -335,7 +334,7 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d quote!( Future<$ret> $(DartCodeOracle::fn_name(func.name()))($args) { return uniffiRustCallAsync( - () => $(DartCodeOracle::find_lib_instance()).$(func.ffi_func().name())( + () => $(func.ffi_func().name())( uniffiClonePointer(), $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) ), @@ -352,7 +351,7 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d quote!( $ret $(DartCodeOracle::fn_name(func.name()))($args) { return rustCall((status) { - $(DartCodeOracle::find_lib_instance()).$(func.ffi_func().name())( + $(func.ffi_func().name())( uniffiClonePointer(), $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status ); @@ -362,7 +361,7 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d } else { quote!( $ret $(DartCodeOracle::fn_name(func.name()))($args) { - return rustCall((status) => $lifter($(DartCodeOracle::find_lib_instance()).$(func.ffi_func().name())( + return rustCall((status) => $lifter($(func.ffi_func().name())( uniffiClonePointer(), $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status )), $error_handler); @@ -454,7 +453,7 @@ fn trait_method_call( ) -> dart::Tokens { assert_eq!(method.arguments().len(), arg_exprs.len()); - let lib_instance = DartCodeOracle::find_lib_instance(); + let ffi_name = method.ffi_func().name(); let error_handler = if let Some(error_type) = method.throws_type() { @@ -475,7 +474,7 @@ fn trait_method_call( type_helper.include_once_check(&ret.as_codetype().canonical_name(), ret); let lifter = quote!($(ret.as_codetype().lift())); quote!( - rustCall((status) => $lifter($lib_instance.$ffi_name( + rustCall((status) => $lifter($ffi_name( uniffiClonePointer(), $(for arg in lowered_args => $arg,) status @@ -484,7 +483,7 @@ fn trait_method_call( } else { quote!( rustCall((status) { - $lib_instance.$ffi_name( + $ffi_name( uniffiClonePointer(), $(for arg in lowered_args => $arg,) status @@ -500,7 +499,7 @@ fn generate_trait_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> let cls_name = &DartCodeOracle::class_name(obj.name()); let impl_name = format!("_{cls_name}Impl"); let finalizer_field = format!("_{cls_name}ImplFinalizer"); - let lib_instance = &DartCodeOracle::find_lib_instance(); + let ffi_object_free_name = obj.ffi_object_free().name(); let ffi_object_clone_name = obj.ffi_object_clone().name(); @@ -556,7 +555,7 @@ fn generate_trait_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> static final Finalizer> $(&finalizer_field) = Finalizer>((ptr) { - rustCall((status) => $lib_instance.$ffi_object_free_name(ptr, status)); + rustCall((status) => $ffi_object_free_name(ptr, status)); }); Pointer _ptr; @@ -564,13 +563,13 @@ fn generate_trait_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> static int allocationSize($(&impl_name) _) => 8; Pointer uniffiClonePointer() { - return rustCall((status) => $lib_instance.$ffi_object_clone_name(_ptr, status)); + return rustCall((status) => $ffi_object_clone_name(_ptr, status)); } @override void dispose() { $(&finalizer_field).detach(this); - rustCall((status) => $lib_instance.$ffi_object_free_name(_ptr, status)); + rustCall((status) => $ffi_object_free_name(_ptr, status)); } $(for method in concrete_methods => $method) diff --git a/src/gen/oracle.rs b/src/gen/oracle.rs index d642404..a26d428 100644 --- a/src/gen/oracle.rs +++ b/src/gen/oracle.rs @@ -91,9 +91,6 @@ impl DartCodeOracle { // } // } - pub fn find_lib_instance() -> dart::Tokens { - quote!(_UniffiLib.instance) - } pub fn infer_ffi_module(ci: &ComponentInterface, fallback: F) -> String where @@ -259,20 +256,22 @@ impl DartCodeOracle { } } + /// With @Native, async functions are called directly by name pub fn async_poll(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { let ffi_func = callable.ffi_rust_future_poll(ci); - quote!($(Self::find_lib_instance()).$ffi_func) + quote!($ffi_func) } + /// With @Native, async functions are called directly by name pub fn async_complete(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { let ffi_func = callable.ffi_rust_future_complete(ci); - let call = quote!($(Self::find_lib_instance()).$ffi_func); - call + quote!($ffi_func) } + /// With @Native, async functions are called directly by name pub fn async_free(callable: impl Callable, ci: &ComponentInterface) -> dart::Tokens { let ffi_func = callable.ffi_rust_future_free(ci); - quote!($(Self::find_lib_instance()).$ffi_func) + quote!($ffi_func) } /// Get the idiomatic Dart rendering of a class name based on `Type`. diff --git a/src/gen/types.rs b/src/gen/types.rs index e64ac71..f23503d 100644 --- a/src/gen/types.rs +++ b/src/gen/types.rs @@ -244,11 +244,11 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { external Pointer data; static RustBuffer alloc(int size) { - return rustCall((status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_alloc().name())(size, status)); + return rustCall((status) => $(self.ci.ffi_rustbuffer_alloc().name())(size, status)); } static RustBuffer fromBytes(ForeignBytes bytes) { - return rustCall((status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_from_bytes().name())(bytes, status)); + return rustCall((status) => $(self.ci.ffi_rustbuffer_from_bytes().name())(bytes, status)); } // static RustBuffer from(Pointer bytes, int len) { @@ -257,11 +257,11 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { // } void free() { - rustCall((status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_free().name())(this, status)); + rustCall((status) => $(self.ci.ffi_rustbuffer_free().name())(this, status)); } RustBuffer reserve(int additionalCapacity) { - return rustCall((status) => $(DartCodeOracle::find_lib_instance()).$(self.ci.ffi_rustbuffer_reserve().name())(this, additionalCapacity, status)); + return rustCall((status) => $(self.ci.ffi_rustbuffer_reserve().name())(this, additionalCapacity, status)); } Uint8List asUint8List() { diff --git a/src/testing.rs b/src/testing.rs index 93515ec..f939cbd 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -150,32 +150,113 @@ fn run_test_impl( println!("Test files will be preserved after completion (no-delete mode)"); } + // Get package name from config (default to "uniffi") + let package_name = if let Some(config_path) = config_path.as_deref() { + // Try to read package name from config + if let Ok(config_content) = std::fs::read_to_string(config_path) { + if let Ok(config_toml) = toml::from_str::(&config_content) { + config_toml + .get("bindings") + .and_then(|b| b.get("dart")) + .and_then(|d| d.get("package_name")) + .and_then(|p| p.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| "uniffi".to_string()) + } else { + "uniffi".to_string() + } + } else { + "uniffi".to_string() + } + } else { + "uniffi".to_string() + }; + let mut pubspec = File::create(out_dir.join("pubspec.yaml"))?; pubspec.write_all( - b" - name: uniffi_test - description: testing module for uniffi - version: 1.0.0 - - environment: - sdk: '>=3.5.0' - dev_dependencies: - test: ^1.24.3 - dependencies: - ffi: ^2.0.1 - ", + format!( + r#" +name: {} +description: testing module for uniffi +version: 1.0.0 + +environment: + sdk: '>=3.10.0' +dev_dependencies: + test: ^1.24.3 +dependencies: + ffi: ^2.0.1 + code_assets: any + hooks: any +"#, + package_name + ) + .as_bytes(), )?; pubspec.flush()?; let test_outdir = out_dir.join("test"); create_dir_all(&test_outdir)?; + // Create hook directory for Native Assets + let hook_dir = out_dir.join("hook"); + create_dir_all(&hook_dir)?; + + // Copy cdylib and get its path test_helper.copy_cdylib_to_out_dir(&out_dir)?; + let cdylib_path = test_helper.cdylib_path()?; + let cdylib_filename = cdylib_path.file_name().unwrap(); + + // Get the asset ID (should match what's in generated Dart code) + // Format: "uniffi:{cdylib_name}" (without package prefix - that's added by Dart automatically) + let cdylib_stem = cdylib_path + .file_stem() + .unwrap() + .trim_start_matches("lib"); // Remove "lib" prefix on Unix + + // Create Native Assets build hook using modern hooks package + let mut build_hook = File::create(hook_dir.join("build.dart"))?; + build_hook.write_all( + format!( + r#"import 'dart:io'; +import 'package:code_assets/code_assets.dart'; +import 'package:hooks/hooks.dart'; + +// Generated by uniffi-dart – this is the full asset ID that Dart will use +// Dart automatically prefixes the 'name' with 'package:$packageName/', so we construct the full ID here +String _makeAssetId(String packageName) => 'package:$packageName/uniffi:{}'; + +void main(List args) async {{ + await build(args, (input, output) async {{ + // The native library is in the package root (copied by test infrastructure) + final libPath = input.packageRoot.resolve('{}'); + + // Register the asset - Dart will automatically prefix the name with package:$packageName/ + // So if name is "uniffi:hello_world", the full ID becomes "package:uniffi/uniffi:hello_world" + output.assets.code.add( + CodeAsset( + package: input.packageName, + name: 'uniffi:{}', // Dart prefixes this with "package:$packageName/" + linkMode: DynamicLoadingBundled(), + file: libPath, + ), + ); + + // Verify the asset ID matches what's in generated code + print('Registered asset: ${{_makeAssetId(input.packageName)}}'); + }}); +}} +"#, + cdylib_stem, cdylib_filename, cdylib_stem + ) + .as_bytes(), + )?; + build_hook.flush()?; gen::generate_dart_bindings( &udl_path, config_path.as_deref(), Some(&out_dir), &test_helper.cdylib_path()?, - false, + false, // library_mode )?; // Copy fixture test files to output directory From 405eb0a08a0dab51e2f9ca418dbf8b282a56cc60 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Tue, 25 Nov 2025 00:41:44 +0100 Subject: [PATCH 09/16] update callback & trait interfaces for UniFFI 0.30.0 - Add free + clone entries to callback vtables (0.30.0 layout) - Implement proper handle-cloning via UniffiHandleMap for callbacks - Wire uniffiFree/uniffiClone into vtable init - Add trait handle LSB check and reject foreign trait implementations --- src/gen/callback_interface.rs | 29 +++++++++++++++++++++++++---- src/gen/objects.rs | 14 +++++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/gen/callback_interface.rs b/src/gen/callback_interface.rs index 7baad3f..071238e 100644 --- a/src/gen/callback_interface.rs +++ b/src/gen/callback_interface.rs @@ -7,8 +7,6 @@ use crate::gen::oracle::{AsCodeType, DartCodeOracle}; use crate::gen::render::AsRenderable; use crate::gen::render::{Renderable, TypeHelperRenderer}; -// Removed problematic context structure - will implement simpler improvements - #[derive(Debug)] pub struct CallbackInterfaceCodeType { name: String, @@ -199,6 +197,8 @@ fn generate_callback_methods_signatures( tokens.append(quote! { typedef UniffiCallbackInterface$(callback_name)Free = Void Function(Uint64); typedef UniffiCallbackInterface$(callback_name)FreeDart = void Function(int); + typedef UniffiCallbackInterface$(callback_name)Clone = Uint64 Function(Uint64); + typedef UniffiCallbackInterface$(callback_name)CloneDart = int Function(int); }); tokens @@ -213,10 +213,11 @@ pub fn generate_callback_vtable_interface( quote! { final class $vtable_name extends Struct { + external Pointer> uniffiFree; + external Pointer> uniffiClone; $(for (index, m) in &methods_vec => external Pointer> $(DartCodeOracle::fn_name(m.name())); ) - external Pointer> uniffiFree; } } } @@ -287,6 +288,11 @@ pub fn generate_callback_functions( let free_callback_pointer = &format!("{}FreePointer", DartCodeOracle::fn_name(callback_name)); let free_callback_type = &format!("UniffiCallbackInterface{callback_name}Free"); + // Clone callback + let clone_callback_fn = &format!("{}CloneCallback", DartCodeOracle::fn_name(callback_name)); + let clone_callback_pointer = &format!("{}ClonePointer", DartCodeOracle::fn_name(callback_name)); + let clone_callback_type = &format!("UniffiCallbackInterface{callback_name}Clone"); + quote! { $(functions) @@ -300,6 +306,20 @@ pub fn generate_callback_functions( final Pointer> $free_callback_pointer = Pointer.fromFunction<$free_callback_type>($free_callback_fn); + + int $clone_callback_fn(int handle) { + try { + final obj = FfiConverterCallbackInterface$cls_name._handleMap.get(handle); + final newHandle = FfiConverterCallbackInterface$cls_name._handleMap.insert(obj); + return newHandle; + } catch (e) { + // Return 0 on error, which should trigger an error on the Rust side + return 0; + } + } + + final Pointer> $clone_callback_pointer = + Pointer.fromFunction<$clone_callback_type>($clone_callback_fn, 0); } } @@ -324,10 +344,11 @@ pub fn generate_callback_interface_vtable_init_function( } $(&vtable_static_instance_name) = calloc<$vtable_name>(); + $(&vtable_static_instance_name).ref.uniffiFree = $(format!("{}FreePointer", DartCodeOracle::fn_name(callback_name))); + $(&vtable_static_instance_name).ref.uniffiClone = $(format!("{}ClonePointer", DartCodeOracle::fn_name(callback_name))); $(for m in methods { $(&vtable_static_instance_name).ref.$(DartCodeOracle::fn_name(m.name())) = $(DartCodeOracle::fn_name(callback_name))$(DartCodeOracle::class_name(m.name()))Pointer; }) - $(&vtable_static_instance_name).ref.uniffiFree = $(format!("{}FreePointer", DartCodeOracle::fn_name(callback_name))); rustCall((status) { uniffi_$(ffi_module)_fn_init_callback_vtable_$(snake_callback)( diff --git a/src/gen/objects.rs b/src/gen/objects.rs index 6c73003..a964ba6 100644 --- a/src/gen/objects.rs +++ b/src/gen/objects.rs @@ -515,7 +515,19 @@ fn generate_trait_object(obj: &Object, type_helper: &dyn TypeHelperRenderer) -> quote! { abstract class $cls_name { - factory $cls_name.lift(Pointer ptr) => $(&impl_name)._internal(ptr); + factory $cls_name.lift(Pointer ptr) { + // UniFFI 0.30.0: Check if handle is from foreign side (lowest bit set) + final handle = ptr.address; + final isForeign = (handle & 0x1) != 0; + + if (isForeign) { + // Foreign (Dart-side) trait implementations not yet supported + throw UnsupportedError("Foreign trait implementations are not yet supported in uniffi-dart"); + } + + // Rust-generated handle (lowest bit is 0) + return $(&impl_name)._internal(ptr); + } static Pointer lower($cls_name value) { if (value is $(&impl_name)) { From d53684851d0b2374f20c213e628892b3a207bf48 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Tue, 25 Nov 2025 21:03:08 +0100 Subject: [PATCH 10/16] fix: support for flat error enums in UDL --- src/gen/enums.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/gen/enums.rs b/src/gen/enums.rs index adbbf19..c009254 100644 --- a/src/gen/enums.rs +++ b/src/gen/enums.rs @@ -61,8 +61,33 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: let dart_cls_name = &DartCodeOracle::class_name(obj.name()); let ffi_converter_name = &obj.as_codetype().ffi_converter_name(); if obj.is_flat() { + let is_error_enum = type_helper.get_ci().is_name_used_as_error(obj.name()); + let implements_exception = if is_error_enum { + quote!( implements Exception) + } else { + quote!() + }; + + // For flat error enums, generate an error handler + let error_handler_class = if is_error_enum { + let error_handler_name = format!("{dart_cls_name}ErrorHandler"); + let instance_name = dart_cls_name.to_lower_camel_case(); + quote! { + class $(&error_handler_name) extends UniffiRustCallStatusErrorHandler { + @override + Exception lift(RustBuffer errorBuf) { + return $ffi_converter_name.lift(errorBuf); + } + } + + final $(&error_handler_name) $(instance_name)ErrorHandler = $(&error_handler_name)(); + } + } else { + quote!() + }; + quote! { - enum $dart_cls_name { + enum $dart_cls_name $implements_exception { $(for variant in obj.variants() => $(DartCodeOracle::enum_variant_name(variant.name())),) ; @@ -103,6 +128,8 @@ pub fn generate_enum(obj: &Enum, type_helper: &dyn TypeHelperRenderer) -> dart:: return 4; } } + + $error_handler_class } } else { let mut variants = vec![]; From d8af62750522ec12f2f30900117909ce4926fc4a Mon Sep 17 00:00:00 2001 From: kumulynja Date: Tue, 25 Nov 2025 21:22:08 +0100 Subject: [PATCH 11/16] fix: toml type signatures --- src/gen/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gen/mod.rs b/src/gen/mod.rs index 95c0224..281462a 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -258,7 +258,7 @@ impl BindingGenerator for DartBindingGenerator { Ok(()) } - fn new_config(&self, root_toml: &toml::Value) -> Result { + fn new_config(&self, root_toml: &toml::value::Value) -> Result { Ok( match root_toml.get("bindings").and_then(|b| b.get("dart")) { Some(v) => v.clone().try_into()?, @@ -307,8 +307,8 @@ impl BindgenCrateConfigSupplier for ConfigFileSupplier { let mut reader = std::io::BufReader::new(file); let mut content = String::new(); reader.read_to_string(&mut content)?; - let toml_value: toml::Value = toml::from_str(&content)?; - if let toml::Value::Table(table) = toml_value { + let toml_value: toml::value::Value = toml::from_str(&content)?; + if let toml::value::Value::Table(table) = toml_value { Ok(Some(table)) } else { Ok(None) From 54f9258b7e4a81f521c8c3eca4f27df8298034cb Mon Sep 17 00:00:00 2001 From: kumulynja Date: Tue, 25 Nov 2025 21:43:17 +0100 Subject: [PATCH 12/16] Fix missing FfiConverter generation for primitives used only in optionals When a primitive type (like u16) was only used within optional fields (u16?) and never as a bare type, uniffi-dart would generate FfiConverterOptionalUInt16 that referenced FfiConverterUInt16, but would never generate the base converter itself, causing compilation errors. The issue was in the compound type (Optional/Sequence) rendering logic, which only rendered inner helpers for Sequence types. Now it also renders helpers for primitive types (Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, Float64) when they haven't been registered yet. This ensures that when Optional is encountered, both FfiConverterUInt16 and FfiConverterOptionalUInt16 are generated. --- src/gen/compounds.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/gen/compounds.rs b/src/gen/compounds.rs index 83e576b..03deecf 100644 --- a/src/gen/compounds.rs +++ b/src/gen/compounds.rs @@ -80,8 +80,17 @@ macro_rules! impl_renderable_for_compound { let _inner_type_signature = if inner_data_type.contains("Float") { "double" } else { "int" }; - let inner_helper = if matches!(self.inner(), Type::Sequence { .. }) && !inner_already_registered { - self.inner().as_renderable().render_type_helper(type_helper) + // Render inner helper for Sequences and primitives that haven't been rendered yet + let inner_helper = if !inner_already_registered { + match self.inner() { + Type::Sequence { .. } + | Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 + | Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 + | Type::Float32 | Type::Float64 => { + self.inner().as_renderable().render_type_helper(type_helper) + } + _ => quote!() + } } else { quote!() }; From 63095b5d393614aefec1724eef5f8761eb9f418a Mon Sep 17 00:00:00 2001 From: kumulynja Date: Tue, 25 Nov 2025 21:59:59 +0100 Subject: [PATCH 13/16] fix: Add explicit import to resolve type ambiguity --- src/gen/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gen/mod.rs b/src/gen/mod.rs index 281462a..f64af82 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -9,6 +9,7 @@ use camino::Utf8Path; use genco::fmt; use genco::prelude::*; use serde::{Deserialize, Serialize}; +use toml; use uniffi_bindgen::BindgenCrateConfigSupplier; use uniffi_bindgen::Component; // use uniffi_bindgen::MergeWith; From 23a506f66c0b79f4457ea685f3ad441d422bbab3 Mon Sep 17 00:00:00 2001 From: kumulynja Date: Wed, 26 Nov 2025 01:49:04 +0100 Subject: [PATCH 14/16] Fix rustCall to check errors before deserializing results The previous implementation of rustCall would call the FFI function and immediately deserialize the result before checking if an error occurred. When a Rust function throws an error, the FFI returns garbage data in the result buffer, causing RangeError or other deserialization failures when Dart tries to lift the invalid data. This fix introduces rustCallWithLifter which: 1. Calls the FFI function to get the raw result 2. Checks the error status 3. Only deserializes (lifts) the result if no error occurred Changes: - Add rustCallWithLifter() function in types.rs that separates FFI call from result lifting - Update function generation in functions.rs to use rustCallWithLifter - Update object method generation in objects.rs to use rustCallWithLifter - Keep original rustCall() for void-returning functions This ensures error handling happens before any deserialization, preventing crashes when functions throw expected errors. --- src/gen/functions.rs | 10 +++++++--- src/gen/objects.rs | 26 +++++++++++++++++--------- src/gen/types.rs | 12 ++++++++++++ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/gen/functions.rs b/src/gen/functions.rs index 13834f0..5240bde 100644 --- a/src/gen/functions.rs +++ b/src/gen/functions.rs @@ -59,9 +59,13 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) } else { quote!( $ret $(DartCodeOracle::fn_name(func.name()))($args) { - return rustCall((status) => $lifter($(func.ffi_func().name())( - $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status - )), $error_handler); + return rustCallWithLifter( + (status) => $(func.ffi_func().name())( + $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status + ), + $lifter, + $error_handler + ); } ) } diff --git a/src/gen/objects.rs b/src/gen/objects.rs index a964ba6..94b14df 100644 --- a/src/gen/objects.rs +++ b/src/gen/objects.rs @@ -361,10 +361,14 @@ pub fn generate_method(func: &Method, type_helper: &dyn TypeHelperRenderer) -> d } else { quote!( $ret $(DartCodeOracle::fn_name(func.name()))($args) { - return rustCall((status) => $lifter($(func.ffi_func().name())( - uniffiClonePointer(), - $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status - )), $error_handler); + return rustCallWithLifter( + (status) => $(func.ffi_func().name())( + uniffiClonePointer(), + $(for arg in &func.arguments() => $(DartCodeOracle::lower_arg_with_callback_handling(arg)),) status + ), + $lifter, + $error_handler + ); } ) } @@ -474,11 +478,15 @@ fn trait_method_call( type_helper.include_once_check(&ret.as_codetype().canonical_name(), ret); let lifter = quote!($(ret.as_codetype().lift())); quote!( - rustCall((status) => $lifter($ffi_name( - uniffiClonePointer(), - $(for arg in lowered_args => $arg,) - status - )), $error_handler) + rustCallWithLifter( + (status) => $ffi_name( + uniffiClonePointer(), + $(for arg in lowered_args => $arg,) + status + ), + $lifter, + $error_handler + ) ) } else { quote!( diff --git a/src/gen/types.rs b/src/gen/types.rs index f23503d..8c6b8e1 100644 --- a/src/gen/types.rs +++ b/src/gen/types.rs @@ -222,6 +222,18 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { } } + // New version that separates FFI call from lifting to avoid deserializing garbage on error + T rustCallWithLifter(F Function(Pointer) ffiCall, T Function(F) lifter, [UniffiRustCallStatusErrorHandler? errorHandler]) { + final status = calloc(); + try { + final rawResult = ffiCall(status); + checkCallStatus(errorHandler ?? NullRustCallStatusErrorHandler(), status); + return lifter(rawResult); + } finally { + calloc.free(status); + } + } + class NullRustCallStatusErrorHandler extends UniffiRustCallStatusErrorHandler { @override Exception lift(RustBuffer errorBuf) { From c46231f56cb3aea1041ea414de0df5057c1d3cd4 Mon Sep 17 00:00:00 2001 From: spacebear Date: Mon, 24 Nov 2025 21:58:28 -0500 Subject: [PATCH 15/16] Fix for async functions lifter --- src/gen/functions.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/gen/functions.rs b/src/gen/functions.rs index 5240bde..8e2936d 100644 --- a/src/gen/functions.rs +++ b/src/gen/functions.rs @@ -32,6 +32,18 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) // Use centralized callback-aware argument lowering if func.is_async() { + // For async methods returning objects, we need to convert the int pointer to Pointer + let async_lifter = if let Some(ret_type) = func.return_type() { + match ret_type { + uniffi_bindgen::interface::Type::Object { .. } => { + quote!((ptr) => $lifter(Pointer.fromAddress(ptr))) + } + _ => lifter.clone(), + } + } else { + lifter.clone() + }; + quote!( Future<$ret> $(DartCodeOracle::fn_name(func.name()))($args) { return uniffiRustCallAsync( @@ -41,7 +53,7 @@ pub fn generate_function(func: &Function, type_helper: &dyn TypeHelperRenderer) $(DartCodeOracle::async_poll(func, type_helper.get_ci())), $(DartCodeOracle::async_complete(func, type_helper.get_ci())), $(DartCodeOracle::async_free(func, type_helper.get_ci())), - $lifter, + $async_lifter, $error_handler, ); } From 0a9e223d611c760f14905855eb7ad39619109628 Mon Sep 17 00:00:00 2001 From: spacebear Date: Tue, 25 Nov 2025 20:43:32 -0500 Subject: [PATCH 16/16] Fix foreign handles by ensuring lowest bit is set Per the uniffi changelog: "Foreign handles must always have the lowest bit set". --- src/gen/types.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gen/types.rs b/src/gen/types.rs index 8c6b8e1..9b61bd9 100644 --- a/src/gen/types.rs +++ b/src/gen/types.rs @@ -422,12 +422,15 @@ impl Renderer<(FunctionDefinition, dart::Tokens)> for TypeHelpersRenderer<'_> { } } + // As of uniffi 0.30, foreign handles must always have the lowest bit set + // This is achieved here with an odd number sequence. class UniffiHandleMap { final Map _map = {}; - int _counter = 0; + int _counter = 1; int insert(T obj) { - final handle = _counter++; + final handle = _counter; + _counter += 2; _map[handle] = obj; return handle; }