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/callback_interface.rs b/src/gen/callback_interface.rs index 136f3d5..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,13 +344,14 @@ 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) { - _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/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/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!() }; 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..c009254 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}; @@ -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![]; diff --git a/src/gen/functions.rs b/src/gen/functions.rs index d771dea..8e2936d 100644 --- a/src/gen/functions.rs +++ b/src/gen/functions.rs @@ -32,16 +32,28 @@ 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( - () => $(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())), $(DartCodeOracle::async_complete(func, type_helper.get_ci())), $(DartCodeOracle::async_free(func, type_helper.get_ci())), - $lifter, + $async_lifter, $error_handler, ); } @@ -50,7 +62,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,9 +71,13 @@ 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())( - $(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/mod.rs b/src/gen/mod.rs index 4f8da92..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; @@ -39,6 +40,7 @@ pub struct Config { cdylib_name: Option, #[serde(default)] external_packages: HashMap, + asset_id: Option, } impl From<&ComponentInterface> for Config { @@ -47,6 +49,7 @@ impl From<&ComponentInterface> for Config { package_name: Some(ci.namespace().to_owned()), cdylib_name: Some(ci.namespace().to_owned()), external_packages: HashMap::new(), + asset_id: None, } } } @@ -67,6 +70,17 @@ impl Config { "uniffi".into() } } + + 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()) + } + } } pub struct DartWrapper<'a> { @@ -86,12 +100,12 @@ impl<'a> DartWrapper<'a> { } fn generate(&self) -> dart::Tokens { - let package_name = self.config.package_name(); - let libname = self.config.cdylib_name(); + 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 @@ -104,99 +118,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 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._(); - - static final DynamicLibrary _dylib = _open(); - - 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}"); - } - - static final _UniffiLib instance = _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))); - $(uniffi_function_definitions(self.ci)) + $(functions_definitions) - 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"); - } - } + // FFI function definitions using @Native + $(uniffi_function_definitions(self.ci, "_uniffiAssetId")) - 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(); } } } @@ -283,6 +296,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::Value = toml::from_str(&content)?; + if let toml::value::Value::Table(table) = toml_value { + Ok(Some(table)) + } else { + Ok(None) + } + } +} + pub fn generate_dart_bindings( udl_file: &Utf8Path, config_file_override: Option<&Utf8Path>, @@ -291,17 +325,28 @@ pub fn generate_dart_bindings( library_mode: bool, ) -> anyhow::Result<()> { if library_mode { + // 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(udl_file.to_string()), + 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 27a66f5..94b14df 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; @@ -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 @@ -190,8 +189,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); } @@ -229,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 { @@ -253,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 @@ -275,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 @@ -315,17 +319,29 @@ 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( - () => $(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)),) ), $(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, ); } @@ -335,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 ); @@ -345,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($(DartCodeOracle::find_lib_instance()).$(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 + ); } ) } @@ -420,6 +440,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 + } } } @@ -433,7 +457,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() { @@ -454,16 +478,20 @@ 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( - 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!( rustCall((status) { - $lib_instance.$ffi_name( + $ffi_name( uniffiClonePointer(), $(for arg in lowered_args => $arg,) status @@ -479,7 +507,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(); @@ -495,7 +523,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)) { @@ -535,7 +575,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; @@ -543,13 +583,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 5baed9b..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 @@ -152,7 +149,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 +177,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 +201,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))>), @@ -262,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/primitives/macros.rs b/src/gen/primitives/macros.rs index 6d0c746..ef30df2 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 { diff --git a/src/gen/types.rs b/src/gen/types.rs index e64ac71..9b61bd9 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) { @@ -244,11 +256,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 +269,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() { @@ -410,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; } diff --git a/src/testing.rs b/src/testing.rs index 2300e06..f939cbd 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) } @@ -158,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( - 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 - ", + pubspec.write_all( + 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