diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index f36a5bda2f..051ee0fa8c 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -274,14 +274,19 @@ impl KotlinCodeOracle { } } - fn ffi_type_label_by_value(ffi_type: &FfiType) -> String { + /// Get the idiomatic Python rendering of an FFI callback function + fn ffi_callback_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + fn ffi_type_label_by_value(&self, ffi_type: &FfiType) -> String { match ffi_type { - FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)), - _ => Self::ffi_type_label(ffi_type), + FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type)), + _ => self.ffi_type_label(ffi_type), } } - fn ffi_type_label(ffi_type: &FfiType) -> String { + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { // Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not // support them yet. Thus, we use the signed variants to represent both signed and unsigned @@ -297,13 +302,11 @@ impl KotlinCodeOracle { format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) } FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(), + FfiType::Callback(name) => self.ffi_callback_name(name), FfiType::ForeignCallback => "ForeignCallback".to_string(), FfiType::ForeignExecutorHandle => "USize".to_string(), FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(), FfiType::RustFutureHandle => "Pointer".to_string(), - FfiType::RustFutureContinuationCallback => { - "UniFffiRustFutureContinuationCallbackType".to_string() - } FfiType::RustFutureContinuationData => "USize".to_string(), } } @@ -469,11 +472,11 @@ pub mod filters { /// Get the Kotlin syntax for representing a given low-level `FfiType`. pub fn ffi_type_name(type_: &FfiType) -> Result { - Ok(KotlinCodeOracle::ffi_type_label(type_)) + Ok(KotlinCodeOracle.ffi_type_label(type_)) } pub fn ffi_type_name_by_value(type_: &FfiType) -> Result { - Ok(KotlinCodeOracle::ffi_type_label_by_value(type_)) + Ok(KotlinCodeOracle.ffi_type_label_by_value(type_)) } // Some FfiTypes have the same ffi_type_label - this makes a vec of them unique. @@ -483,7 +486,7 @@ pub mod filters { let mut seen = HashSet::new(); let mut result = Vec::new(); for t in types { - if seen.insert(KotlinCodeOracle::ffi_type_label(&t)) { + if seen.insert(KotlinCodeOracle.ffi_type_label(&t)) { result.push(t) } } @@ -523,6 +526,11 @@ pub mod filters { .type_label()) } + /// Get the idiomatic Kotlin rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result { + Ok(KotlinCodeOracle.ffi_callback_name(nm)) + } + pub fn error_canonical_name(as_type: &impl AsType) -> Result { Ok(KotlinCodeOracle .find_as_error(&as_type.as_type()) diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt index 4d1c099a02..4f4ece37f7 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -1,20 +1,20 @@ // Async return type handlers -internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort() -internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort() +internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte() +internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte() -internal val uniffiContinuationHandleMap = UniFfiHandleMap>() +internal val uniffiContinuationHandleMap = UniFfiHandleMap>() // FFI type for Rust future continuations -internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType { - override fun callback(continuationHandle: USize, pollResult: Short) { - uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult) +internal object uniffiRustFutureContinuationCallback: UniffiRustFutureContinuationCallback { + override fun callback(data: USize, pollResult: Byte) { + uniffiContinuationHandleMap.remove(data)?.resume(pollResult) } } internal suspend fun uniffiRustCallAsync( rustFuture: Pointer, - pollFunc: (Pointer, UniFffiRustFutureContinuationCallbackType, USize) -> Unit, + pollFunc: (Pointer, UniffiRustFutureContinuationCallback, USize) -> Unit, completeFunc: (Pointer, RustCallStatus) -> F, freeFunc: (Pointer) -> Unit, liftFunc: (F) -> T, @@ -22,7 +22,7 @@ internal suspend fun uniffiRustCallAsync( ): T { try { do { - val pollResult = suspendCancellableCoroutine { continuation -> + val pollResult = suspendCancellableCoroutine { continuation -> pollFunc( rustFuture, uniffiRustFutureContinuationCallback, diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt index 382a5f7413..eb878d61e3 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -154,8 +154,3 @@ internal class UniFfiHandleMap { return map.remove(handle) } } - -// FFI type for Rust future continuations -internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback { - fun callback(continuationHandle: USize, pollResult: Short); -} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt index 6a3aeada35..7d877281f4 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt @@ -13,6 +13,22 @@ private inline fun loadIndirect( return Native.load(findLibraryName(componentName), Lib::class.java) } +// Define FFI callback types +{%- for callback in ci.ffi_callback_definitions() %} +internal interface {{ callback.name()|ffi_callback_name }} : com.sun.jna.Callback { + fun callback( + {%- for arg in callback.arguments() -%} + {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name }}, + {%- endfor -%} + ) + {%- match callback.return_type() %} + {%- when Some(return_type) %}: {{ return_type|ffi_type_name }}, + {%- when None %} + {%- endmatch %} +} + +{%- endfor %} + // A JNA Library to expose the extern-C FFI definitions. // This is an implementation detail which will be called internally by the public API. diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 9199bbefd0..1607592497 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -299,7 +299,12 @@ impl PythonCodeOracle { fixup_keyword(nm.to_string().to_shouty_snake_case()) } - fn ffi_type_label(ffi_type: &FfiType) -> String { + /// Get the idiomatic Python rendering of an FFI callback function + fn ffi_callback_name(&self, nm: &str) -> String { + format!("UNIFFI_{}", nm.to_shouty_snake_case()) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "ctypes.c_int8".to_string(), FfiType::UInt8 => "ctypes.c_uint8".to_string(), @@ -317,12 +322,12 @@ impl PythonCodeOracle { None => "_UniffiRustBuffer".to_string(), }, FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(), + FfiType::Callback(name) => self.ffi_callback_name(name), FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(), // Pointer to an `asyncio.EventLoop` instance FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(), FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(), FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), - FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(), FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), } } @@ -439,7 +444,7 @@ pub mod filters { /// Get the Python syntax for representing a given low-level `FfiType`. pub fn ffi_type_name(type_: &FfiType) -> Result { - Ok(PythonCodeOracle::ffi_type_label(type_)) + Ok(PythonCodeOracle.ffi_type_label(type_)) } /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). @@ -462,6 +467,11 @@ pub mod filters { Ok(PythonCodeOracle.enum_variant_name(nm)) } + /// Get the idiomatic Python rendering of a FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result { + Ok(PythonCodeOracle.ffi_callback_name(nm)) + } + /// Get the idiomatic Python rendering of an individual enum variant. pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { Ok(PythonCodeOracle.object_names(obj)) diff --git a/uniffi_bindgen/src/bindings/python/templates/Async.py b/uniffi_bindgen/src/bindings/python/templates/Async.py index 51bc31b595..aae87ab98b 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Async.py +++ b/uniffi_bindgen/src/bindings/python/templates/Async.py @@ -7,7 +7,7 @@ # Continuation callback for async functions # lift the return value or error and resolve the future, causing the async function to resume. -@_UNIFFI_FUTURE_CONTINUATION_T +@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK def _uniffi_continuation_callback(future_ptr, poll_code): (eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr) eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) diff --git a/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/uniffi_bindgen/src/bindings/python/templates/Helpers.py index dca962f176..e8eeff7b8a 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Helpers.py +++ b/uniffi_bindgen/src/bindings/python/templates/Helpers.py @@ -70,6 +70,3 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): # Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int` _UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer)) -# UniFFI future continuation -_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8) - diff --git a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py index fac6cd5564..3b030d6a70 100644 --- a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -68,6 +68,20 @@ def _uniffi_check_api_checksums(lib): pass {%- endfor %} + +# Define FFI callback types +{%- for callback in ci.ffi_callback_definitions() %} +{{ callback.name()|ffi_callback_name }} = ctypes.CFUNCTYPE( + {%- match callback.return_type() %} + {%- when Some(return_type) %}{{ return_type|ffi_type_name }}, + {%- when None %}None, + {%- endmatch %} + {%- for arg in callback.arguments() -%} + {{ arg.type_().borrow()|ffi_type_name }}, + {%- endfor -%} +) +{%- endfor %} + # A ctypes library to expose the extern-C FFI definitions. # This is an implementation detail which will be called internally by the public API. diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index e6defe65cc..d4db12f2cb 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -153,6 +153,7 @@ mod filters { FfiType::RustArcPtr(_) => ":pointer".to_string(), FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(), FfiType::ForeignBytes => "ForeignBytes".to_string(), + FfiType::Callback(_) => unimplemented!("FFI Callbacks not implemented"), // Callback interfaces are not yet implemented, but this needs to return something in // order for the coverall tests to pass. FfiType::ForeignCallback => ":pointer".to_string(), @@ -162,9 +163,7 @@ mod filters { FfiType::ForeignExecutorHandle => { unimplemented!("Foreign executors are not implemented") } - FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback - | FfiType::RustFutureContinuationData => { + FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { unimplemented!("Async functions are not implemented") } }) diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 1ba385d361..311c75ab4c 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -443,6 +443,11 @@ impl SwiftCodeOracle { nm.to_string().to_lower_camel_case() } + /// Get the idiomatic Python rendering of an FFI callback function + fn ffi_callback_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "Int8".into(), @@ -458,10 +463,10 @@ impl SwiftCodeOracle { FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), + FfiType::Callback(name) => self.ffi_callback_name(name), FfiType::ForeignCallback => "ForeignCallback".into(), FfiType::ForeignExecutorHandle => "Int".into(), FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(), - FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(), FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { "UnsafeMutableRawPointer".into() } @@ -473,7 +478,6 @@ impl SwiftCodeOracle { FfiType::ForeignCallback | FfiType::ForeignExecutorCallback | FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback | FfiType::RustFutureContinuationData => { format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type)) } @@ -574,12 +578,12 @@ pub mod filters { FfiType::RustArcPtr(_) => "void*_Nonnull".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), + FfiType::Callback(name) => { + format!("{} _Nonnull", SwiftCodeOracle.ffi_callback_name(name)) + } FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(), FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(), FfiType::ForeignExecutorHandle => "size_t".into(), - FfiType::RustFutureContinuationCallback => { - "UniFfiRustFutureContinuation _Nonnull".into() - } FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { "void* _Nonnull".into() } @@ -617,6 +621,11 @@ pub mod filters { Ok(oracle().enum_variant_name(nm)) } + /// Get the idiomatic Swift rendering of an ffi callback function name + pub fn ffi_callback_name(nm: &str) -> Result { + Ok(oracle().ffi_callback_name(nm)) + } + pub fn error_handler(result: &ResultType) -> Result { Ok(match &result.throws_type { Some(t) => format!("{}.lift", ffi_converter_name(t)?), diff --git a/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/uniffi_bindgen/src/bindings/swift/templates/Async.swift index f947408182..578ff4ddf4 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Async.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -3,7 +3,7 @@ private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 fileprivate func uniffiRustCallAsync( rustFutureFunc: () -> UnsafeMutableRawPointer, - pollFunc: (UnsafeMutableRawPointer, @escaping UniFfiRustFutureContinuation, UnsafeMutableRawPointer) -> (), + pollFunc: (UnsafeMutableRawPointer, @escaping UniffiRustFutureContinuationCallback, UnsafeMutableRawPointer) -> (), completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer) -> F, freeFunc: (UnsafeMutableRawPointer) -> (), liftFunc: (F) throws -> T, diff --git a/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h index 87698e359f..8bc53febbf 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h @@ -59,8 +59,17 @@ typedef struct RustCallStatus { // ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ #endif // def UNIFFI_SHARED_H -// Continuation callback for UniFFI Futures -typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t); +// Define FFI callback types +{%- for callback in ci.ffi_callback_definitions() %} +typedef + {%- match callback.return_type() %}{% when Some(return_type) %} {{ return_type|ffi_type_name }} {% when None %} void {% endmatch -%} + (*{{ callback.name()|ffi_callback_name }})( + {%- for arg in callback.arguments() -%} + {{ arg.type_().borrow()|header_ffi_type_name }} + {%- if !loop.last %}, {% endif %} + {%- endfor -%} + ); +{%- endfor %} // Scaffolding functions {%- for func in ci.iter_ffi_function_definitions() %} diff --git a/uniffi_bindgen/src/interface/ffi.rs b/uniffi_bindgen/src/interface/ffi.rs index 8f23ccbb90..e3c8bfbfd6 100644 --- a/uniffi_bindgen/src/interface/ffi.rs +++ b/uniffi_bindgen/src/interface/ffi.rs @@ -47,6 +47,9 @@ pub enum FfiType { /// A borrowed reference to some raw bytes owned by foreign language code. /// The provider of this reference must keep it alive for the duration of the receiving call. ForeignBytes, + /// Pointer to a callback function. The inner type is the name of the callback, which matches + /// one of the items in [crate::ComponentInterface::ffi_callback_definitions]. + Callback(String), /// Pointer to a callback function that handles all callbacks on the foreign language side. ForeignCallback, /// Pointer-sized opaque handle that represents a foreign executor. Foreign bindings can @@ -56,8 +59,6 @@ pub enum FfiType { ForeignExecutorCallback, /// Pointer to a Rust future RustFutureHandle, - /// Continuation function for a Rust future - RustFutureContinuationCallback, RustFutureContinuationData, // TODO: you can imagine a richer structural typesystem here, e.g. `Ref` or something. // We don't need that yet and it's possible we never will, so it isn't here for now. @@ -225,14 +226,52 @@ pub struct FfiArgument { } impl FfiArgument { + pub fn new(name: impl Into, type_: FfiType) -> Self { + Self { + name: name.into(), + type_, + } + } + pub fn name(&self) -> &str { &self.name } + pub fn type_(&self) -> FfiType { self.type_.clone() } } +/// Represents an "extern C"-style callback function +/// +/// These are defined in the foreign code and passed to Rust as a function pointer. +#[derive(Debug, Default, Clone)] +pub struct FfiCallbackFunction { + // Name for this function type. This matches the value inside `FfiType::Callback` + pub(super) name: String, + pub(super) arguments: Vec, + pub(super) return_type: Option, + pub(super) has_rust_call_status_arg: bool, +} + +impl FfiCallbackFunction { + pub fn name(&self) -> &str { + &self.name + } + + pub fn arguments(&self) -> Vec<&FfiArgument> { + self.arguments.iter().collect() + } + + pub fn return_type(&self) -> Option<&FfiType> { + self.return_type.as_ref() + } + + pub fn has_rust_call_status_arg(&self) -> bool { + self.has_rust_call_status_arg + } +} + #[cfg(test)] mod test { // There's not really much to test here to be honest, diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index c56a3cd65c..099fd653a1 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -67,7 +67,7 @@ mod record; pub use record::{Field, Record}; pub mod ffi; -pub use ffi::{FfiArgument, FfiFunction, FfiType}; +pub use ffi::{FfiArgument, FfiCallbackFunction, FfiFunction, FfiType}; pub use uniffi_meta::Radix; use uniffi_meta::{ ConstructorMetadata, LiteralMetadata, NamespaceMetadata, ObjectMetadata, TraitMethodMetadata, @@ -204,6 +204,21 @@ impl ComponentInterface { self.objects.iter().find(|o| o.name == name) } + /// Get the definitions for callback FFI functions + /// + /// These are defined by the foreign code and invoked by Rust. + pub fn ffi_callback_definitions(&self) -> impl IntoIterator { + [FfiCallbackFunction { + name: "RustFutureContinuationCallback".to_owned(), + arguments: vec![ + FfiArgument::new("data", FfiType::RustFutureContinuationData), + FfiArgument::new("poll_result", FfiType::Int8), + ], + return_type: None, + has_rust_call_status_arg: false, + }] + } + /// Get the definitions for every Callback Interface type in the interface. pub fn callback_interface_definitions(&self) -> &[CallbackInterface] { &self.callback_interfaces @@ -447,7 +462,7 @@ impl ComponentInterface { }, FfiArgument { name: "callback".to_owned(), - type_: FfiType::RustFutureContinuationCallback, + type_: FfiType::Callback("RustFutureContinuationCallback".to_owned()), }, FfiArgument { name: "callback_data".to_owned(),