diff --git a/Cargo.lock b/Cargo.lock index a3a018f69b..5594d0ac8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -819,16 +819,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1805,18 +1795,6 @@ dependencies = [ "uniffi", ] -[[package]] -name = "uniffi-fixture-reexport-scaffolding-macro" -version = "0.22.0" -dependencies = [ - "cargo_metadata", - "libloading", - "uniffi", - "uniffi-fixture-callbacks", - "uniffi-fixture-coverall", - "uniffi_bindgen", -] - [[package]] name = "uniffi-fixture-regression-callbacks-omit-labels" version = "0.22.0" diff --git a/Cargo.toml b/Cargo.toml index 4d919af4a0..f6a852dd02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ members = [ "fixtures/keywords/swift", "fixtures/metadata", "fixtures/proc-macro", - "fixtures/reexport-scaffolding-macro", "fixtures/regressions/enum-without-i32-helpers", "fixtures/regressions/fully-qualified-types", "fixtures/regressions/kotlin-experimental-unsigned-types", diff --git a/fixtures/keywords/kotlin/src/keywords.udl b/fixtures/keywords/kotlin/src/keywords.udl index 6cdd7a6f07..f9a6c76597 100644 --- a/fixtures/keywords/kotlin/src/keywords.udl +++ b/fixtures/keywords/kotlin/src/keywords.udl @@ -7,7 +7,8 @@ interface break { void object(u8? class); }; -callback interface continue { +[Trait] +interface continue { return return(return v); continue? continue(); record break(break? v); diff --git a/fixtures/keywords/kotlin/src/lib.rs b/fixtures/keywords/kotlin/src/lib.rs index d5c63ab780..bfdd8bd117 100644 --- a/fixtures/keywords/kotlin/src/lib.rs +++ b/fixtures/keywords/kotlin/src/lib.rs @@ -15,9 +15,9 @@ impl r#break { } #[allow(non_camel_case_types)] -trait r#continue { +trait r#continue: Send + Sync { fn r#return(&self, v: r#return) -> r#return; - fn r#continue(&self) -> Option>; + fn r#continue(&self) -> Option>; fn r#break(&self, _v: Option>) -> HashMap>; fn r#while(&self, _v: Vec) -> r#while; fn class(&self, _v: HashMap>) -> Option>>; diff --git a/fixtures/keywords/rust/src/keywords.udl b/fixtures/keywords/rust/src/keywords.udl index 1edc1cdb6b..a66335953a 100644 --- a/fixtures/keywords/rust/src/keywords.udl +++ b/fixtures/keywords/rust/src/keywords.udl @@ -13,7 +13,8 @@ interface break { void async(u8? yield); }; -callback interface continue { +[Trait] +interface continue { return return(return v); continue? continue(); record break(break? v); diff --git a/fixtures/keywords/rust/src/lib.rs b/fixtures/keywords/rust/src/lib.rs index 0b93557307..b3a7610350 100644 --- a/fixtures/keywords/rust/src/lib.rs +++ b/fixtures/keywords/rust/src/lib.rs @@ -16,15 +16,15 @@ impl r#break { pub fn r#break(&self, v: HashMap>) -> Option>> { Some(v) } - fn r#continue(&self, _v: Vec>) {} + fn r#continue(&self, _v: Vec>) {} pub fn r#yield(&self, _async: u8) {} pub fn r#async(&self, _yield: Option) {} } #[allow(non_camel_case_types)] -pub trait r#continue { +pub trait r#continue: Send + Sync { fn r#return(&self, v: r#return) -> r#return; - fn r#continue(&self) -> Option>; + fn r#continue(&self) -> Option>; fn r#break(&self, _v: Option>) -> HashMap>; fn r#while(&self, _v: Vec) -> r#while; fn r#yield(&self, _v: HashMap>) -> Option>>; diff --git a/fixtures/proc-macro/tests/bindings/test_proc_macro.swift b/fixtures/proc-macro/tests/bindings/test_proc_macro.swift index 1232b406bd..c01799203b 100644 --- a/fixtures/proc-macro/tests/bindings/test_proc_macro.swift +++ b/fixtures/proc-macro/tests/bindings/test_proc_macro.swift @@ -79,8 +79,7 @@ class SwiftTestCallbackInterface : TestCallbackInterface { } func callbackHandler(h: Object) -> UInt32 { - var v = h.takeError(e: BasicError.InvalidInput) - return v + return h.takeError(e: BasicError.InvalidInput) } } diff --git a/fixtures/reexport-scaffolding-macro/Cargo.toml b/fixtures/reexport-scaffolding-macro/Cargo.toml deleted file mode 100644 index d494000c26..0000000000 --- a/fixtures/reexport-scaffolding-macro/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "uniffi-fixture-reexport-scaffolding-macro" -version = "0.22.0" -authors = ["Firefox Sync Team "] -edition = "2021" -license = "MPL-2.0" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -name = "reexport_scaffolding_macro" -crate-type = ["lib", "cdylib"] - -[dependencies] -uniffi-fixture-callbacks = { path = "../callbacks" } -uniffi-fixture-coverall = { path = "../coverall" } -uniffi = { path = "../../uniffi", version = "0.25" } -uniffi_bindgen = { path = "../../uniffi_bindgen" } - -[dev-dependencies] -cargo_metadata = "0.15" -libloading = "0.7" -uniffi = { path = "../../uniffi", version = "0.25" } diff --git a/fixtures/reexport-scaffolding-macro/src/lib.rs b/fixtures/reexport-scaffolding-macro/src/lib.rs deleted file mode 100644 index 6bd04f2ccd..0000000000 --- a/fixtures/reexport-scaffolding-macro/src/lib.rs +++ /dev/null @@ -1,197 +0,0 @@ -uniffi_fixture_callbacks::uniffi_reexport_scaffolding!(); -uniffi_coverall::uniffi_reexport_scaffolding!(); - -#[cfg(test)] -mod tests { - use cargo_metadata::Message; - use libloading::{Library, Symbol}; - use std::ffi::CString; - use std::os::raw::c_void; - use std::process::{Command, Stdio}; - use uniffi::{FfiConverter, ForeignCallback, RustBuffer, RustCallStatus, RustCallStatusCode}; - use uniffi_bindgen::ComponentInterface; - - struct UniFfiTag; - - // Load the dynamic library that was built for this crate. The external functions from - // `uniffi_callbacks' and `uniffi_coverall` should be present. - pub fn load_library() -> Library { - let mut cmd = Command::new("cargo"); - cmd.arg("build").arg("--message-format=json").arg("--lib"); - cmd.stdout(Stdio::piped()); - let mut child = cmd.spawn().unwrap(); - let output = std::io::BufReader::new(child.stdout.take().unwrap()); - let artifacts = Message::parse_stream(output) - .filter_map(|message| match message { - Err(e) => panic!("{e}"), - Ok(Message::CompilerArtifact(artifact)) => { - if artifact.target.name == "reexport_scaffolding_macro" - && artifact.target.kind.iter().any(|item| item == "cdylib") - { - Some(artifact) - } else { - None - } - } - _ => None, - }) - .collect::>(); - if !child.wait().unwrap().success() { - panic!("Failed to execute `cargo build`"); - } - let artifact = match artifacts.len() { - 1 => &artifacts[0], - n => panic!("Found {n} artfiacts from cargo build"), - }; - let cdylib_files: Vec<_> = artifact - .filenames - .iter() - .filter(|nm| matches!(nm.extension(), Some(std::env::consts::DLL_EXTENSION))) - .collect(); - let library_path = match cdylib_files.len() { - 1 => cdylib_files[0].to_string(), - _ => panic!("Failed to build exactly one cdylib file"), - }; - unsafe { Library::new(library_path).unwrap() } - } - - pub fn has_symbol(library: &Library, name: &str) -> bool { - unsafe { - library - .get::(CString::new(name).unwrap().as_bytes_with_nul()) - .is_ok() - } - } - - pub fn get_symbol<'lib, T>(library: &'lib Library, name: &str) -> Symbol<'lib, T> { - unsafe { - library - .get::(CString::new(name).unwrap().as_bytes_with_nul()) - .unwrap() - } - } - - #[test] - fn test_symbols_present() { - let library = load_library(); - let coveralls_ci = ComponentInterface::from_webidl( - include_str!("../../coverall/src/coverall.udl"), - "uniffi_coverall", - ) - .unwrap(); - let callbacks_ci = ComponentInterface::from_webidl( - include_str!("../../callbacks/src/callbacks.udl"), - "uniffi_fixture_callbacks", - ) - .unwrap(); - - // UniFFI internal function - assert!(has_symbol::< - unsafe extern "C" fn(i32, &mut RustCallStatus) -> RustBuffer, - >( - &library, coveralls_ci.ffi_rustbuffer_alloc().name() - )); - - // Top-level function - assert!( - has_symbol:: u64>( - &library, - coveralls_ci - .get_function_definition("get_num_alive") - .unwrap() - .ffi_func() - .name() - ) - ); - - // Object method - assert!( - has_symbol:: u64>( - &library, - coveralls_ci - .get_object_definition("Coveralls") - .unwrap() - .get_method("get_name") - .ffi_func() - .name() - ) - ); - - // Callback init func - assert!(has_symbol::< - unsafe extern "C" fn(ForeignCallback, &mut RustCallStatus) -> (), - >( - &library, - callbacks_ci - .get_callback_interface_definition("ForeignGetters") - .unwrap() - .ffi_init_callback() - .name() - )); - } - - #[test] - fn test_calls() { - let mut call_status = RustCallStatus::default(); - let library = load_library(); - let coveralls_ci = ComponentInterface::from_webidl( - include_str!("../../coverall/src/coverall.udl"), - "uniffi_coverall", - ) - .unwrap(); - let object_def = coveralls_ci.get_object_definition("Coveralls").unwrap(); - - let get_num_alive: Symbol u64> = get_symbol( - &library, - coveralls_ci - .get_function_definition("get_num_alive") - .unwrap() - .ffi_func() - .name(), - ); - let coveralls_new: Symbol< - unsafe extern "C" fn(RustBuffer, &mut RustCallStatus) -> *const c_void, - > = get_symbol( - &library, - object_def.primary_constructor().unwrap().ffi_func().name(), - ); - let coveralls_get_name: Symbol< - unsafe extern "C" fn(*const c_void, &mut RustCallStatus) -> RustBuffer, - > = get_symbol( - &library, - object_def.get_method("get_name").ffi_func().name(), - ); - let coveralls_free: Symbol ()> = - get_symbol(&library, object_def.ffi_object_free().name()); - - let num_alive = unsafe { get_num_alive(&mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!(num_alive, 0); - - let obj_id = unsafe { - coveralls_new( - >::lower("TestName".into()), - &mut call_status, - ) - }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - - let name_buf = unsafe { coveralls_get_name(obj_id, &mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!( - >::try_lift(name_buf).unwrap(), - "TestName" - ); - - let num_alive = unsafe { get_num_alive(&mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!(num_alive, 1); - - unsafe { coveralls_free(obj_id, &mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - - let num_alive = unsafe { get_num_alive(&mut call_status) }; - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!(num_alive, 0); - } -} diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index 051ee0fa8c..187123cc15 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -252,7 +252,12 @@ impl KotlinCodeOracle { /// Get the idiomatic Kotlin rendering of a variable name. fn var_name(&self, nm: &str) -> String { - format!("`{}`", nm.to_string().to_lower_camel_case()) + format!("`{}`", self.var_name_raw(nm)) + } + + /// `var_name` without the backticks. Useful for using in `@Structure.FieldOrder`. + fn var_name_raw(&self, nm: &str) -> String { + nm.to_string().to_lower_camel_case() } /// Get the idiomatic Kotlin rendering of an individual enum variant. @@ -274,11 +279,16 @@ impl KotlinCodeOracle { } } - /// Get the idiomatic Python rendering of an FFI callback function + /// Get the idiomatic Kotlin rendering of an FFI callback function name fn ffi_callback_name(&self, nm: &str) -> String { format!("Uniffi{}", nm.to_upper_camel_case()) } + /// Get the idiomatic Kotlin rendering of an FFI struct name + fn ffi_struct_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)), @@ -286,6 +296,24 @@ impl KotlinCodeOracle { } } + fn ffi_type_label_by_reference(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::Int8 + | FfiType::UInt8 + | FfiType::Int16 + | FfiType::UInt16 + | FfiType::Int32 + | FfiType::UInt32 + | FfiType::Int64 + | FfiType::UInt64 + | FfiType::Float32 + | FfiType::Float64 => format!("{}ByReference", self.ffi_type_label(ffi_type)), + // JNA structs default to ByReference + FfiType::RustBuffer(_) | FfiType::Struct(_) => self.ffi_type_label(ffi_type), + _ => panic!("{ffi_type:?} by reference is not implemented"), + } + } + 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 @@ -303,10 +331,12 @@ impl KotlinCodeOracle { } FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(), FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_name(name), FfiType::ForeignCallback => "ForeignCallback".to_string(), FfiType::ForeignExecutorHandle => "USize".to_string(), FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(), - FfiType::RustFutureHandle => "Pointer".to_string(), + FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner), + FfiType::VoidPointer | FfiType::RustFutureHandle => "Pointer".to_string(), FfiType::RustFutureContinuationData => "USize".to_string(), } } @@ -503,6 +533,11 @@ pub mod filters { Ok(KotlinCodeOracle.var_name(nm)) } + /// `var_name` without the backticks. Useful for using in `@Structure.FieldOrder`. + pub fn var_name_raw(nm: &str) -> Result { + Ok(KotlinCodeOracle.var_name_raw(nm)) + } + pub fn variant_name(v: &Variant) -> Result { Ok(KotlinCodeOracle.enum_variant_name(v.name())) } @@ -531,6 +566,11 @@ pub mod filters { Ok(KotlinCodeOracle.ffi_callback_name(nm)) } + /// Get the idiomatic Kotlin rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result { + Ok(KotlinCodeOracle.ffi_struct_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/CallbackInterfaceImpl.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt index f1c58ee971..d111d1151c 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt @@ -1,107 +1,70 @@ {% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %} -// Implement the foreign callback handler for {{ interface_name }} -internal class {{ callback_handler_class }} : ForeignCallback { - @Suppress("TooGenericExceptionCaught") - override fun invoke(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { - val cb = {{ ffi_converter_name }}.handleMap.get(handle) - return when (method) { - IDX_CALLBACK_FREE -> { - {{ ffi_converter_name }}.handleMap.remove(handle) +{%- let trait_impl=format!("uniffiCallbackInterface{name}") %} - // Successful return - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - UNIFFI_CALLBACK_SUCCESS - } - {% for meth in methods.iter() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - {{ loop.index }} -> { - // Call the method, write to outBuf and return a status code - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info - try { - this.{{ method_name }}(cb, argsData, argsLen, outBuf) - } catch (e: Throwable) { - // Unexpected error - try { - // Try to serialize the error into a string - outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString())) - } catch (e: Throwable) { - // If that fails, then it's time to give up and just return - } - UNIFFI_CALLBACK_UNEXPECTED_ERROR - } - } - {% endfor %} - else -> { - // An unexpected error happened. - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - try { - // Try to serialize the error into a string - outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index")) - } catch (e: Throwable) { - // If that fails, then it's time to give up and just return - } - UNIFFI_CALLBACK_UNEXPECTED_ERROR +// Put the implementation in an object so we don't pollute the top-level namespace +internal object {{ trait_impl }} { + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + internal object {{ meth.name()|var_name }}: {{ ffi_callback.name()|ffi_callback_name }} { + override fun callback( + {%- for arg in ffi_callback.arguments() -%} + {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }}, + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() -%} + uniffiCallStatus: RustCallStatus, + {%- endif -%} + ) + {%- match ffi_callback.return_type() %} + {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }}, + {%- when None %} + {%- endmatch %} { + val uniffiObj = {{ ffi_converter_name }}.handleMap.get(uniffiHandle) + val makeCall = { -> + uniffiObj.{{ meth.name()|fn_name() }}( + {%- for arg in meth.arguments() %} + {{ arg|lift_fn }}({{ arg.name()|var_name }}), + {%- endfor %} + ) } - } - } - {% for meth in methods.iter() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name %} - @Suppress("UNUSED_PARAMETER") - private fun {{ method_name }}(kotlinCallbackInterface: {{ interface_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { - {%- if meth.arguments().len() > 0 %} - val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also { - it.order(ByteOrder.BIG_ENDIAN) - } - {%- endif %} + {%- match meth.return_type() %} + {%- when Some(return_type) %} + val writeReturn = { value: {{ return_type|type_name }} -> uniffiOutReturn.setValue({{ return_type|lower_fn }}(value)) } + {%- when None %} + val writeReturn = { _: Unit -> Unit } + {%- endmatch %} - {%- match meth.return_type() %} - {%- when Some with (return_type) %} - fun makeCall() : Int { - val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}( - {%- for arg in meth.arguments() %} - {{ arg|read_fn }}(argsBuf) - {% if !loop.last %}, {% endif %} - {%- endfor %} - ) - outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue)) - return UNIFFI_CALLBACK_SUCCESS - } - {%- when None %} - fun makeCall() : Int { - kotlinCallbackInterface.{{ meth.name()|fn_name }}( - {%- for arg in meth.arguments() %} - {{ arg|read_fn }}(argsBuf) - {%- if !loop.last %}, {% endif %} - {%- endfor %} + {%- match meth.throws_type() %} + {%- when None %} + uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) + {%- when Some(error_type) %} + uniffiTraitInterfaceCallWithError( + uniffiCallStatus, + makeCall, + writeReturn, + { e: {{error_type|error_type_name }} -> {{ error_type|lower_fn }}(e) } ) - return UNIFFI_CALLBACK_SUCCESS + {%- endmatch %} } - {%- endmatch %} + } + {%- endfor %} - {%- match meth.throws_type() %} - {%- when None %} - fun makeCallAndHandleError() : Int = makeCall() - {%- when Some(error_type) %} - fun makeCallAndHandleError() : Int = try { - makeCall() - } catch (e: {{ error_type|error_type_name }}) { - // Expected error, serialize it into outBuf - outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e)) - UNIFFI_CALLBACK_ERROR + internal object uniffiFree: {{ "CallbackInterfaceFree"|ffi_callback_name }} { + override fun callback(handle: Long) { + {{ ffi_converter_name }}.handleMap.remove(handle) } - {%- endmatch %} - - return makeCallAndHandleError() } - {% endfor %} + + internal var vtable = {{ vtable|ffi_type_name }}( + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + {{ meth.name()|var_name() }}, + {%- endfor %} + uniffiFree + ) // Registers the foreign callback with the Rust side. // This method is generated for each callback interface. internal fun register(lib: _UniFFILib) { - lib.{{ ffi_init_callback.name() }}(this) + lib.{{ ffi_init_callback.name() }}(vtable) } } - -internal val {{ callback_handler_obj }} = {{ callback_handler_class }}() diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt index 59a127b1a2..f5afcae7eb 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -1,9 +1,9 @@ {%- let cbi = ci|get_callback_interface_definition(name) %} -{%- let callback_handler_class = format!("UniffiCallbackInterface{}", name) %} -{%- let callback_handler_obj = format!("uniffiCallbackInterface{}", name) %} {%- let ffi_init_callback = cbi.ffi_init_callback() %} {%- let interface_name = cbi|type_name %} {%- let methods = cbi.methods() %} +{%- let vtable = cbi.vtable() %} +{%- let vtable_methods = cbi.vtable_methods() %} {% include "Interface.kt" %} {% include "CallbackInterfaceImpl.kt" %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt index eb878d61e3..c623c37734 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -1,6 +1,10 @@ // A handful of classes and functions to support the generated data structures. // This would be a good candidate for isolating in its own ffi-support lib. -// Error runtime. + +internal const val UNIFFI_CALL_SUCCESS = 0.toByte() +internal const val UNIFFI_CALL_ERROR = 1.toByte() +internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() + @Structure.FieldOrder("code", "error_buf") internal open class RustCallStatus : Structure() { @JvmField var code: Byte = 0 @@ -9,15 +13,15 @@ internal open class RustCallStatus : Structure() { class ByValue: RustCallStatus(), Structure.ByValue fun isSuccess(): Boolean { - return code == 0.toByte() + return code == UNIFFI_CALL_SUCCESS } fun isError(): Boolean { - return code == 1.toByte() + return code == UNIFFI_CALL_ERROR } fun isPanic(): Boolean { - return code == 2.toByte() + return code == UNIFFI_CALL_UNEXPECTED_ERROR } } @@ -118,6 +122,37 @@ public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, tru } } +internal inline fun uniffiTraitInterfaceCall( + callStatus: RustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, +) { + try { + writeReturn(makeCall()) + } catch(e: Exception) { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString()) + } +} + +internal inline fun uniffiTraitInterfaceCallWithError( + callStatus: RustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, + lowerError: (E) -> RustBuffer.ByValue +) { + try { + writeReturn(makeCall()) + } catch(e: Exception) { + if (e is E) { + callStatus.code = UNIFFI_CALL_ERROR + callStatus.error_buf = lowerError(e) + } else { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString()) + } + } +} // Map handles to objects // diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt index 7d877281f4..4a273c9341 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt @@ -18,15 +18,27 @@ private inline fun loadIndirect( 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 }}, + {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }}, {%- endfor -%} + {%- if callback.has_rust_call_status_arg() %} + uniffiCallStatus: RustCallStatus, + {%- endif %} ) {%- match callback.return_type() %} - {%- when Some(return_type) %}: {{ return_type|ffi_type_name }}, + {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }}, {%- when None %} {%- endmatch %} } +{%- endfor %} +// Define FFI structs +{%- for ffi_struct in ci.ffi_struct_definitions() %} +@Structure.FieldOrder({% for field in ffi_struct.fields() %}"{{ field.name()|var_name_raw }}"{% if !loop.last %}, {% endif %}{% endfor %}) +internal class {{ ffi_struct.name()|ffi_struct_name }}( + {%- for field in ffi_struct.fields() %} + @JvmField internal var {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name }}, + {%- endfor %} +) : Structure() { } {%- endfor %} // A JNA Library to expose the extern-C FFI definitions. diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt index cef6be881f..8b584893b0 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -97,8 +97,8 @@ class {{ impl_class_name }}( } {%- if obj.is_trait_interface() %} -{%- let callback_handler_class = format!("UniffiCallbackInterface{}", name) %} -{%- let callback_handler_obj = format!("uniffiCallbackInterface{}", name) %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} {%- let ffi_init_callback = obj.ffi_init_callback() %} {% include "CallbackInterfaceImpl.kt" %} {%- endif %} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt index dfbea24074..ca0dc7da0b 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt @@ -11,6 +11,12 @@ open class RustBuffer : Structure() { class ByValue: RustBuffer(), Structure.ByValue class ByReference: RustBuffer(), Structure.ByReference + internal fun setValue(other: RustBuffer) { + capacity = other.capacity + len = other.len + data = other.data + } + companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status) diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 1607592497..a695e15daa 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -299,11 +299,17 @@ impl PythonCodeOracle { fixup_keyword(nm.to_string().to_shouty_snake_case()) } - /// Get the idiomatic Python rendering of an FFI callback function + /// Get the idiomatic Python rendering of an FFI callback function name fn ffi_callback_name(&self, nm: &str) -> String { format!("UNIFFI_{}", nm.to_shouty_snake_case()) } + /// Get the idiomatic Python rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + // The ctypes docs use both SHOUTY_SNAKE_CASE AND UpperCamelCase for structs. Let's shout. + 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(), @@ -323,11 +329,13 @@ impl PythonCodeOracle { }, FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(), FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_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::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)), + FfiType::VoidPointer | FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), } } @@ -467,11 +475,16 @@ pub mod filters { Ok(PythonCodeOracle.enum_variant_name(nm)) } - /// Get the idiomatic Python rendering of a FFI callback function name + /// Get the idiomatic Python rendering of an 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 FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result { + Ok(PythonCodeOracle.ffi_struct_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/CallbackInterfaceImpl.py b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py index f9a4768a5c..f5ea15b6a9 100644 --- a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py +++ b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py @@ -1,91 +1,62 @@ {% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} - -# Declaration and _UniffiConverters for {{ type_name }} Callback Interface - -def {{ callback_handler_class }}(handle, method, args_data, args_len, buf_ptr): - {% for meth in methods.iter() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name %} - def {{ method_name }}(python_callback, args_stream, buf_ptr): - {#- Unpacking args from the _UniffiRustBuffer #} - def makeCall(): - {#- Calling the concrete callback object #} - {%- if meth.arguments().len() != 0 -%} - return python_callback.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {{ arg|read_fn }}(args_stream) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - {%- else %} - return python_callback.{{ meth.name()|fn_name }}() - {%- endif %} - - def makeCallAndHandleReturn(): - {%- match meth.return_type() %} - {%- when Some(return_type) %} - rval = makeCall() - with _UniffiRustBuffer.alloc_with_builder() as builder: - {{ return_type|write_fn }}(rval, builder) - buf_ptr[0] = builder.finalize() - {%- when None %} - makeCall() - {%- endmatch %} - return _UNIFFI_CALLBACK_SUCCESS +{%- let trait_impl="UniffiTraitImpl{name}"|format %} + +# Put all the bits inside a class to keep the top-level namespace clean +class {{ trait_impl }}: + # For each method, generate a callback function to pass to Rust + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + + @{{ ffi_callback.name()|ffi_callback_name }} + def {{ meth.name()|fn_name }}( + uniffi_handle, + {%- for arg in meth.arguments() %} + {{ arg.name()|var_name }}, + {%- endfor %} + return_ptr, + uniffi_call_status_ptr, + ): + uniffi_obj = {{ ffi_converter_name }}._handle_map.get(uniffi_handle) + def make_call(): + args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name()|var_name }}), {% endfor %}) + method = uniffi_obj.{{ meth.name()|fn_name }} + return method(*args) + {%- match meth.return_type() %} + {%- when Some(return_type) %} + def write_return_value(v): + return_ptr[0] = {{ return_type|lower_fn }}(v) + {%- when None %} + write_return_value = lambda v: None + {%- endmatch %} {%- match meth.throws_type() %} {%- when None %} - return makeCallAndHandleReturn() - {%- when Some(err) %} - try: - return makeCallAndHandleReturn() - except {{ err|type_name }} as e: - # Catch errors declared in the UDL file - with _UniffiRustBuffer.alloc_with_builder() as builder: - {{ err|write_fn }}(e, builder) - buf_ptr[0] = builder.finalize() - return _UNIFFI_CALLBACK_ERROR + _uniffi_trait_interface_call( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + ) + {%- when Some(error) %} + _uniffi_trait_interface_call_with_error( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + {{ error|type_name }}, + {{ error|lower_fn }}, + ) {%- endmatch %} - - {% endfor %} - - cb = {{ ffi_converter_name }}._handle_map.get(handle) - - if method == IDX_CALLBACK_FREE: - {{ ffi_converter_name }}._handle_map.remove(handle) - - # Successfull return - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return _UNIFFI_CALLBACK_SUCCESS - - {% for meth in methods.iter() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - if method == {{ loop.index }}: - # Call the method and handle any errors - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for details - try: - return {{ method_name }}(cb, _UniffiRustBufferStream(args_data, args_len), buf_ptr) - except BaseException as e: - # Catch unexpected errors - try: - # Try to serialize the exception into a String - buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e)) - except: - # If that fails, just give up - pass - return _UNIFFI_CALLBACK_UNEXPECTED_ERROR - {% endfor %} - - # This should never happen, because an out of bounds method index won't - # ever be used. Once we can catch errors, we should return an InternalException. - # https://github.com/mozilla/uniffi-rs/issues/351 - - # An unexpected error happened. - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return _UNIFFI_CALLBACK_UNEXPECTED_ERROR - -# We need to keep this function reference alive: -# if they get GC'd while in use then UniFFI internals could attempt to call a function -# that is in freed memory. -# That would be...uh...bad. Yeah, that's the word. Bad. -{{ callback_handler_obj }} = _UNIFFI_FOREIGN_CALLBACK_T({{ callback_handler_class }}) -_UniffiLib.{{ ffi_init_callback.name() }}({{ callback_handler_obj }}) + {%- endfor %} + + @{{ "CallbackInterfaceFree"|ffi_callback_name }} + def uniffi_free(uniffi_handle): + {{ ffi_converter_name }}._handle_map.remove(uniffi_handle) + + # Generate the FFI VTable. This has a field for each callback interface method. + uniffi_vtable = {{ vtable|ffi_type_name }}( + {%- for (_, meth) in vtable_methods.iter() %} + {{ meth.name()|fn_name }}, + {%- endfor %} + uniffi_free + ) + # Send Rust a pointer to the VTable. Note: this means we need to keep the struct alive forever, + # or else bad things will happen when Rust tries to access it. + _UniffiLib.{{ ffi_init_callback.name() }}(ctypes.byref(uniffi_vtable)) diff --git a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py index dbfa094562..f02bcac572 100644 --- a/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py @@ -1,9 +1,9 @@ {%- let cbi = ci|get_callback_interface_definition(name) %} -{%- let callback_handler_class = format!("UniffiCallbackInterface{}", name) %} -{%- let callback_handler_obj = format!("uniffiCallbackInterface{}", name) %} {%- let ffi_init_callback = cbi.ffi_init_callback() %} {%- let protocol_name = type_name.clone() %} +{%- let vtable = cbi.vtable() %} {%- let methods = cbi.methods() %} +{%- let vtable_methods = cbi.vtable_methods() %} {% include "Protocol.py" %} {% include "CallbackInterfaceImpl.py" %} diff --git a/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/uniffi_bindgen/src/bindings/python/templates/Helpers.py index e8eeff7b8a..3123c7e70f 100644 --- a/uniffi_bindgen/src/bindings/python/templates/Helpers.py +++ b/uniffi_bindgen/src/bindings/python/templates/Helpers.py @@ -16,15 +16,15 @@ class _UniffiRustCallStatus(ctypes.Structure): # These match the values from the uniffi::rustcalls module CALL_SUCCESS = 0 CALL_ERROR = 1 - CALL_PANIC = 2 + CALL_UNEXPECTED_ERROR = 2 def __str__(self): if self.code == _UniffiRustCallStatus.CALL_SUCCESS: return "_UniffiRustCallStatus(CALL_SUCCESS)" elif self.code == _UniffiRustCallStatus.CALL_ERROR: return "_UniffiRustCallStatus(CALL_ERROR)" - elif self.code == _UniffiRustCallStatus.CALL_PANIC: - return "_UniffiRustCallStatus(CALL_PANIC)" + elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)" else: return "_UniffiRustCallStatus()" @@ -53,7 +53,7 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): raise InternalError("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") else: raise error_ffi_converter.lift(call_status.error_buf) - elif call_status.code == _UniffiRustCallStatus.CALL_PANIC: + elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer # with the message. But if that code panics, then it just sends back # an empty buffer. @@ -66,6 +66,25 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): raise InternalError("Invalid _UniffiRustCallStatus code: {}".format( call_status.code)) +def _uniffi_trait_interface_call(call_status, make_call, write_return_value): + try: + return write_return_value(make_call()) + except Exception as e: + import traceback + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e)) + +def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error): + try: + try: + return write_return_value(make_call()) + except error_type as e: + call_status.code = _UniffiRustCallStatus.CALL_ERROR + call_status.error_buf = lower_error(e) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e)) + # A function pointer for a callback as defined by UniFFI. # 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)) diff --git a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py index 3b030d6a70..d3f5b20bca 100644 --- a/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -79,9 +79,22 @@ def _uniffi_check_api_checksums(lib): {%- for arg in callback.arguments() -%} {{ arg.type_().borrow()|ffi_type_name }}, {%- endfor -%} + {%- if callback.has_rust_call_status_arg() %} + ctypes.POINTER(_UniffiRustCallStatus), + {%- endif %} ) {%- endfor %} +# Define FFI structs +{%- for ffi_struct in ci.ffi_struct_definitions() %} +class {{ ffi_struct.name()|ffi_struct_name }}(ctypes.Structure): + _fields_ = [ + {%- for field in ffi_struct.fields() %} + ("{{ field.name()|var_name }}", {{ field.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/python/templates/ObjectTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 097fcd4d1d..eb2e2e187e 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -68,9 +68,9 @@ def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: {% endfor %} {%- if obj.is_trait_interface() %} -{%- let callback_handler_class = format!("UniffiCallbackInterface{}", name) %} -{%- let callback_handler_obj = format!("uniffiCallbackInterface{}", name) %} {%- let ffi_init_callback = obj.ffi_init_callback() %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} {% include "CallbackInterfaceImpl.py" %} {%- endif %} diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index d4db12f2cb..4b417d5f1c 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -157,6 +157,13 @@ mod filters { // 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(), + // Note: references are not really used by the Ruby bindings yet, maybe this should + // change to a different type when they are actually implemented. + FfiType::Reference(_) => ":pointer".to_string(), + FfiType::VoidPointer => ":pointer".to_string(), + FfiType::Struct(_) => { + unimplemented!("Structs are not implemented") + } FfiType::ForeignExecutorCallback => { unimplemented!("Foreign executors 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 311c75ab4c..b34fd5162d 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -443,12 +443,17 @@ impl SwiftCodeOracle { nm.to_string().to_lower_camel_case() } - /// Get the idiomatic Python rendering of an FFI callback function + /// Get the idiomatic Swift rendering of an FFI callback function name 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 { + /// Get the idiomatic Swift rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "Int8".into(), FfiType::UInt8 => "UInt8".into(), @@ -464,29 +469,21 @@ impl SwiftCodeOracle { FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_name(name), FfiType::ForeignCallback => "ForeignCallback".into(), FfiType::ForeignExecutorHandle => "Int".into(), FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(), - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "UnsafeMutableRawPointer".into() + FfiType::Reference(inner) => { + format!("UnsafeMutablePointer<{}>", self.ffi_type_label(inner)) } - } - } - - fn ffi_type_label(&self, ffi_type: &FfiType) -> String { - match ffi_type { - FfiType::ForeignCallback - | FfiType::ForeignExecutorCallback + FfiType::VoidPointer | FfiType::RustFutureHandle - | FfiType::RustFutureContinuationData => { - format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type)) - } - _ => self.ffi_type_label_raw(ffi_type), + | FfiType::RustFutureContinuationData => "UnsafeMutableRawPointer".into(), } } fn ffi_canonical_name(&self, ffi_type: &FfiType) -> String { - self.ffi_type_label_raw(ffi_type) + self.ffi_type_label(ffi_type) } /// Get the name of the protocol and class name for an object. @@ -581,12 +578,14 @@ pub mod filters { FfiType::Callback(name) => { format!("{} _Nonnull", SwiftCodeOracle.ffi_callback_name(name)) } + FfiType::Struct(name) => SwiftCodeOracle.ffi_struct_name(name), FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(), FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(), FfiType::ForeignExecutorHandle => "size_t".into(), - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "void* _Nonnull".into() - } + FfiType::Reference(inner) => format!("{}* _Nonnull", header_ffi_type_name(inner)?), + FfiType::VoidPointer + | FfiType::RustFutureHandle + | FfiType::RustFutureContinuationData => "void* _Nonnull".into(), }) } @@ -621,11 +620,16 @@ pub mod filters { Ok(oracle().enum_variant_name(nm)) } - /// Get the idiomatic Swift rendering of an ffi callback function name + /// 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)) } + /// Get the idiomatic Swift rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result { + Ok(oracle().ffi_struct_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/BridgingHeaderTemplate.h b/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h index 8bc53febbf..875252eab0 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h +++ b/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h @@ -62,15 +62,27 @@ typedef struct RustCallStatus { // 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 -%} + {%- match callback.return_type() %}{% when Some(return_type) %} {{ return_type|header_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 %} + {%- if !loop.last || callback.has_rust_call_status_arg() %}, {% endif %} {%- endfor -%} + {%- if callback.has_rust_call_status_arg() %} + RustCallStatus *_Nonnull uniffiCallStatus + {%- endif %} ); {%- endfor %} +// Define FFI structs +{%- for struct in ci.ffi_struct_definitions() %} +typedef struct {{ struct.name()|ffi_struct_name }} { + {%- for field in struct.fields() %} + {{ field.type_().borrow()|header_ffi_type_name }} {{ field.name()|var_name }}; + {%- endfor %} +} {{ struct.name()|ffi_struct_name }}; +{%- endfor %} + // Scaffolding functions {%- for func in ci.iter_ffi_function_definitions() %} {% match func.return_type() -%}{%- when Some with (type_) %}{{ type_|header_ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}( diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift index 157da46128..fa14c0433e 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift @@ -1,88 +1,61 @@ {%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %} - -// Declaration and FfiConverters for {{ type_name }} Callback Interface - -fileprivate let {{ callback_handler }} : ForeignCallback = - { (handle: UniFFICallbackHandle, method: Int32, argsData: UnsafePointer, argsLen: Int32, out_buf: UnsafeMutablePointer) -> Int32 in - {% for meth in methods.iter() -%} - {%- let method_name = format!("invoke_{}", meth.name())|fn_name %} - - func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer) throws -> Int32 { - {%- if meth.arguments().len() > 0 %} - var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen))) - {%- endif %} - - {%- match meth.return_type() %} - {%- when Some(return_type) %} - func makeCall() throws -> Int32 { - let result = {% if meth.throws() %} try{% endif %} swiftCallbackInterface.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - var writer = [UInt8]() - {{ return_type|write_fn }}(result, into: &writer) - out_buf.pointee = RustBuffer(bytes: writer) - return UNIFFI_CALLBACK_SUCCESS - } - {%- when None %} - func makeCall() throws -> Int32 { - {% if meth.throws() %}try {% endif %}swiftCallbackInterface.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - return UNIFFI_CALLBACK_SUCCESS - } - {%- endmatch %} - - {%- match meth.throws_type() %} - {%- when None %} - return try makeCall() - {%- when Some(error_type) %} - do { - return try makeCall() - } catch let error as {{ error_type|type_name }} { - out_buf.pointee = {{ error_type|lower_fn }}(error) - return UNIFFI_CALLBACK_ERROR - } - {%- endmatch %} - } - {%- endfor %} - - - switch method { - case IDX_CALLBACK_FREE: - {{ ffi_converter_name }}.handleMap.remove(handle: handle) - // Sucessful return - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return UNIFFI_CALLBACK_SUCCESS - {% for meth in methods.iter() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - case {{ loop.index }}: - guard let cb = {{ ffi_converter_name }}.handleMap.get(handle: handle) else { - out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("No callback in handlemap; this is a Uniffi bug") - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } - do { - return try {{ method_name }}(cb, argsData, argsLen, out_buf) - } catch let error { - out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) - return UNIFFI_CALLBACK_UNEXPECTED_ERROR +{%- let trait_impl=format!("UniffiCallbackInterface{name}") %} + +// Put the implementation in a struct so we don't pollute the top-level namespace +fileprivate struct {{ trait_impl }} { + + // Create the VTable using a series of closures. + // Swift automatically converts these into C callback functions. + static var vtable: {{ vtable|ffi_type_name }} = {{ vtable|ffi_type_name }}( + {%- for (ffi_callback, meth) in vtable_methods %} + {{ meth.name()|fn_name }}: { ( + {%- for arg in ffi_callback.arguments() %} + {{ arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name }}, + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() %} + uniffiCallStatus: UnsafeMutablePointer + {%- endif %} + ) in + guard let uniffiObj = {{ ffi_converter_name }}.handleMap.get(handle: uniffiHandle) else { + uniffiCallStatus.pointee.code = CALL_UNEXPECTED_ERROR + uniffiCallStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}("No callback in handlemap; this is a Uniffi bug") + return } - {% endfor %} - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalError. - // https://github.com/mozilla/uniffi-rs/issues/351 - default: - // An unexpected error happened. - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } + let makeCall = { {% if meth.throws() %}try {% endif %}uniffiObj.{{ meth.name()|fn_name }}( + {%- for arg in meth.arguments() %} + {% if !config.omit_argument_labels() %} {{ arg.name()|arg_name }}: {% endif %}try {{ arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} + {%- endfor %} + ) } + {% match meth.return_type() %} + {%- when Some(t) %} + let writeReturn = { uniffiOutReturn.pointee = {{ t|lower_fn }}($0) } + {%- when None %} + let writeReturn = { () } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + uniffiTraitInterfaceCall( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn + ) + {%- when Some(error_type) %} + uniffiTraitInterfaceCallWithError( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn, + lowerError: {{ error_type|lower_fn }} + ) + {%- endmatch %} + }, + {%- endfor %} + uniffiFree: { (uniffiHandle: UInt64) -> () in + {{ ffi_converter_name }}.handleMap.delete(handle: uniffiHandle) + } + ) } private func {{ callback_init }}() { - {{ ffi_init_callback.name() }}({{ callback_handler }}) + {{ ffi_init_callback.name() }}(&{{ trait_impl }}.vtable) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index f9e75b2a3f..0c6643f764 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -3,6 +3,8 @@ {%- let callback_init = format!("uniffiCallbackInit{}", name) %} {%- let methods = cbi.methods() %} {%- let protocol_name = type_name.clone() %} +{%- let vtable = cbi.vtable() %} +{%- let vtable_methods = cbi.vtable_methods() %} {%- let ffi_init_callback = cbi.ffi_init_callback() %} {% include "Protocol.swift" %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift index a34b128e23..1c9560d91b 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift @@ -28,7 +28,7 @@ fileprivate enum UniffiInternalError: LocalizedError { fileprivate let CALL_SUCCESS: Int8 = 0 fileprivate let CALL_ERROR: Int8 = 1 -fileprivate let CALL_PANIC: Int8 = 2 +fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2 fileprivate let CALL_CANCELLED: Int8 = 3 fileprivate extension RustCallStatus { @@ -81,7 +81,7 @@ private func uniffiCheckCallStatus( throw UniffiInternalError.unexpectedRustCallError } - case CALL_PANIC: + case CALL_UNEXPECTED_ERROR: // When the rust code sees a panic, it tries to construct a RustBuffer // with the message. But if that code panics, then it just sends back // an empty buffer. @@ -99,3 +99,33 @@ private func uniffiCheckCallStatus( throw UniffiInternalError.unexpectedRustCallStatusCode } } + +private func uniffiTraitInterfaceCall( + callStatus: UnsafeMutablePointer, + makeCall: () throws -> T, + writeReturn: (T) -> () +) { + do { + try writeReturn(makeCall()) + } catch let error { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + } +} + +private func uniffiTraitInterfaceCallWithError( + callStatus: UnsafeMutablePointer, + makeCall: () throws -> T, + writeReturn: (T) -> (), + lowerError: (E) -> RustBuffer +) { + do { + try writeReturn(makeCall()) + } catch let error as E { + callStatus.pointee.code = CALL_ERROR + callStatus.pointee.errorBuf = lowerError(error) + } catch { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + } +} diff --git a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index b63631f40e..74a9a551cb 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -93,6 +93,8 @@ public class {{ impl_class_name }}: {{ protocol_name }} { {%- if obj.is_trait_interface() %} {%- let callback_handler = format!("uniffiCallbackInterface{}", name) %} {%- let callback_init = format!("uniffiCallbackInit{}", name) %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} {%- let ffi_init_callback = obj.ffi_init_callback() %} {% include "CallbackInterfaceImpl.swift" %} {%- endif %} diff --git a/uniffi_bindgen/src/interface/callbacks.rs b/uniffi_bindgen/src/interface/callbacks.rs index 9bafce25de..87fb18e8c0 100644 --- a/uniffi_bindgen/src/interface/callbacks.rs +++ b/uniffi_bindgen/src/interface/callbacks.rs @@ -33,9 +33,11 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` +use std::iter; + use uniffi_meta::Checksum; -use super::ffi::FfiFunction; +use super::ffi::{FfiArgument, FfiCallbackFunction, FfiField, FfiFunction, FfiStruct, FfiType}; use super::object::Method; use super::{AsType, Type, TypeIterator}; @@ -77,7 +79,32 @@ impl CallbackInterface { } pub(super) fn derive_ffi_funcs(&mut self) { - self.ffi_init_callback = FfiFunction::callback_init(&self.module_path, &self.name); + self.ffi_init_callback = + FfiFunction::callback_init(&self.module_path, &self.name, vtable_name(&self.name)); + } + + /// FfiCallbacks to define for our methods. + pub fn ffi_callbacks(&self) -> Vec { + ffi_callbacks(&self.name, &self.methods) + } + + /// The VTable FFI type + pub fn vtable(&self) -> FfiType { + FfiType::Struct(vtable_name(&self.name)) + } + + /// the VTable struct to define. + pub fn vtable_definition(&self) -> FfiStruct { + vtable_struct(&self.name, &self.methods) + } + + /// Vec of (ffi_callback, method) pairs + pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> { + self.methods + .iter() + .enumerate() + .map(|(i, method)| (method_ffi_callback(&self.name, method, i), method)) + .collect() } pub fn iter_types(&self) -> TypeIterator<'_> { @@ -94,6 +121,62 @@ impl AsType for CallbackInterface { } } +/// [FfiCallbackFunction] functions for the methods of a callback/trait interface +pub fn ffi_callbacks(trait_name: &str, methods: &[Method]) -> Vec { + methods + .into_iter() + .enumerate() + .map(|(i, method)| method_ffi_callback(trait_name, method, i)) + .collect() +} + +pub fn method_ffi_callback(trait_name: &str, method: &Method, index: usize) -> FfiCallbackFunction { + FfiCallbackFunction { + name: method_ffi_callback_name(trait_name, index), + arguments: iter::once(FfiArgument::new("uniffi_handle", FfiType::UInt64)) + .chain(method.arguments().into_iter().map(Into::into)) + .chain(iter::once(match method.return_type() { + Some(t) => FfiArgument::new("uniffi_out_return", FfiType::from(t).reference()), + None => FfiArgument::new("uniffi_out_return", FfiType::VoidPointer), + })) + .collect(), + has_rust_call_status_arg: true, + return_type: None, + } +} + +/// [FfiStruct] for a callback/trait interface VTable +/// +/// This struct has a FfiCallbackFunction field for each method, plus extra fields for special +/// methods +pub fn vtable_struct(trait_name: &str, methods: &[Method]) -> FfiStruct { + FfiStruct { + name: vtable_name(trait_name), + fields: methods + .into_iter() + .enumerate() + .map(|(i, method)| { + FfiField::new( + method.name(), + FfiType::Callback(format!("CallbackInterface{trait_name}Method{i}")), + ) + }) + .chain([FfiField::new( + "uniffi_free", + FfiType::Callback("CallbackInterfaceFree".to_owned()), + )]) + .collect(), + } +} + +pub fn method_ffi_callback_name(trait_name: &str, index: usize) -> String { + format!("CallbackInterface{trait_name}Method{index}") +} + +pub fn vtable_name(trait_name: &str) -> String { + format!("VTableCallbackInterface{trait_name}") +} + #[cfg(test)] mod test { use super::super::ComponentInterface; diff --git a/uniffi_bindgen/src/interface/ffi.rs b/uniffi_bindgen/src/interface/ffi.rs index e3c8bfbfd6..626fcee65c 100644 --- a/uniffi_bindgen/src/interface/ffi.rs +++ b/uniffi_bindgen/src/interface/ffi.rs @@ -47,9 +47,12 @@ 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]. + /// Pointer to a callback function. The inner value which matches one of the items in + /// [crate::ComponentInterface::ffi_callback_definitions]. Callback(String), + /// Pointer to a VTable. The inner value matches one of the items in + /// [crate::ComponentInterface::ffi_struct_definitions]. + Struct(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 @@ -60,8 +63,16 @@ pub enum FfiType { /// Pointer to a Rust future RustFutureHandle, 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. + /// Pointer to an FfiType. + Reference(Box), + /// Opaque pointer + VoidPointer, +} + +impl FfiType { + pub fn reference(self) -> FfiType { + FfiType::Reference(Box::new(self)) + } } /// When passing data across the FFI, each `Type` value will be lowered into a corresponding @@ -151,12 +162,12 @@ pub struct FfiFunction { } impl FfiFunction { - pub fn callback_init(module_path: &str, trait_name: &str) -> Self { + pub fn callback_init(module_path: &str, trait_name: &str, vtable_name: String) -> Self { Self { - name: uniffi_meta::init_callback_fn_symbol_name(module_path, trait_name), + name: uniffi_meta::init_callback_vtable_fn_symbol_name(module_path, trait_name), arguments: vec![FfiArgument { - name: "handle".to_string(), - type_: FfiType::ForeignCallback, + name: "vtable".to_string(), + type_: FfiType::Struct(vtable_name).reference(), }], return_type: None, has_rust_call_status_arg: false, @@ -272,6 +283,49 @@ impl FfiCallbackFunction { } } +/// Represents a repr(C) struct used in the FFI +#[derive(Debug, Default, Clone)] +pub struct FfiStruct { + pub(super) name: String, + pub(super) fields: Vec, +} + +impl FfiStruct { + /// Get the name of the VTable + pub fn name(&self) -> &str { + &self.name + } + + /// Get the ( + pub fn fields(&self) -> &[FfiField] { + &self.fields + } +} + +/// Represents a field of an [FfiStruct] +#[derive(Debug, Clone)] +pub struct FfiField { + pub(super) name: String, + pub(super) type_: FfiType, +} + +impl FfiField { + 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() + } +} + #[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 099fd653a1..e179c9840a 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, FfiCallbackFunction, FfiFunction, FfiType}; +pub use ffi::{FfiArgument, FfiCallbackFunction, FfiFunction, FfiStruct, FfiType}; pub use uniffi_meta::Radix; use uniffi_meta::{ ConstructorMetadata, LiteralMetadata, NamespaceMetadata, ObjectMetadata, TraitMethodMetadata, @@ -207,16 +207,47 @@ impl ComponentInterface { /// 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, - }] + pub fn ffi_callback_definitions(&self) -> impl IntoIterator + '_ { + self.builtin_ffi_callback_definitions().into_iter().chain( + self.callback_interfaces + .iter() + .flat_map(|cbi| cbi.ffi_callbacks()) + .chain(self.objects.iter().flat_map(|o| o.ffi_callbacks())), + ) + } + + fn builtin_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, + }, + FfiCallbackFunction { + name: "CallbackInterfaceFree".to_owned(), + arguments: vec![FfiArgument::new("handle", FfiType::UInt64)], + return_type: None, + has_rust_call_status_arg: false, + }, + ] + } + + /// Get the definitions for callback FFI functions + /// + /// These are defined by the foreign code and invoked by Rust. + pub fn ffi_struct_definitions(&self) -> impl IntoIterator + '_ { + self.callback_interface_definitions() + .into_iter() + .map(|cbi| cbi.vtable_definition()) + .chain( + self.object_definitions() + .into_iter() + .flat_map(|o| o.vtable_definition()), + ) } /// Get the definitions for every Callback Interface type in the interface. diff --git a/uniffi_bindgen/src/interface/object.rs b/uniffi_bindgen/src/interface/object.rs index d79e7fccb1..a21b152fbf 100644 --- a/uniffi_bindgen/src/interface/object.rs +++ b/uniffi_bindgen/src/interface/object.rs @@ -62,7 +62,8 @@ use std::iter; use anyhow::Result; use uniffi_meta::Checksum; -use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::callbacks; +use super::ffi::{FfiArgument, FfiCallbackFunction, FfiFunction, FfiStruct, FfiType}; use super::function::{Argument, Callable}; use super::{AsType, ObjectImpl, Type, TypeIterator}; @@ -195,8 +196,11 @@ impl Object { self.ffi_func_free.return_type = None; self.ffi_func_free.is_object_free_function = true; if self.is_trait_interface() { - self.ffi_init_callback = - Some(FfiFunction::callback_init(&self.module_path, &self.name)); + self.ffi_init_callback = Some(FfiFunction::callback_init( + &self.module_path, + &self.name, + callbacks::vtable_name(&self.name), + )); } for cons in self.constructors.iter_mut() { @@ -212,6 +216,41 @@ impl Object { Ok(()) } + /// For trait interfaces, FfiCallbacks to define for our methods, otherwise an empty vec. + pub fn ffi_callbacks(&self) -> Vec { + if self.is_trait_interface() { + callbacks::ffi_callbacks(&self.name, &self.methods) + } else { + vec![] + } + } + + /// For trait interfaces, the VTable FFI type + pub fn vtable(&self) -> Option { + self.is_trait_interface() + .then(|| FfiType::Struct(callbacks::vtable_name(&self.name))) + } + + /// For trait interfaces, the VTable struct to define. Otherwise None. + pub fn vtable_definition(&self) -> Option { + self.is_trait_interface() + .then(|| callbacks::vtable_struct(&self.name, &self.methods)) + } + + /// Vec of (ffi_callback_name, method) pairs + pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> { + self.methods + .iter() + .enumerate() + .map(|(i, method)| { + ( + callbacks::method_ffi_callback(&self.name, method, i), + method, + ) + }) + .collect() + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new( self.methods diff --git a/uniffi_core/src/ffi/callbackinterface.rs b/uniffi_core/src/ffi/callbackinterface.rs index 7be66880bb..bca056c35b 100644 --- a/uniffi_core/src/ffi/callbackinterface.rs +++ b/uniffi_core/src/ffi/callbackinterface.rs @@ -91,121 +91,20 @@ //! //! Uniffi generates a protocol or interface in client code in the foreign language must implement. //! -//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals` -//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface. +//! For each callback interface, UniFFI defines a VTable. +//! This is a `repr(C)` struct where each field is a `repr(C)` callback function pointer. +//! There is one field for each method, plus an extra field for the `uniffi_free` method. +//! The foreign code registers one VTable per callback interface with Rust. //! -//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the -//! `KeychainCallbackInternals` to store the instance in a handlemap. -//! -//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements -//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to -//! client code as `Box`. -//! -//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`. -//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the -//! object handle, and the method selector. -//! -//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap, -//! and calls the actual implementation of the method. -//! -//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for -//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct -//! type and then returns to client code. +//! VTable methods have a similar signature to Rust scaffolding functions. +//! The one difference is that values are returned via an out pointer to work around a Python bug (https://bugs.python.org/issue5710). //! +//! The foreign object that implements the interface is represented by an opaque handle. +//! UniFFI generates a struct that implements the trait by calling VTable methods, passing the handle as the first parameter. +//! When the struct is dropeed, the `uniffi_free` method is called. -use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer}; use std::fmt; -/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it, -/// and it can be deleted from the handle map. -pub const IDX_CALLBACK_FREE: u32 = 0; - -/// Result of a foreign callback invocation -#[repr(i32)] -#[derive(Debug, PartialEq, Eq)] -pub enum CallbackResult { - /// Successful call. - /// The return value is serialized to `buf_ptr`. - Success = 0, - /// Expected error. - /// This is returned when a foreign method throws an exception that corresponds to the Rust Err half of a Result. - /// The error value is serialized to `buf_ptr`. - Error = 1, - /// Unexpected error. - /// An error message string is serialized to `buf_ptr`. - UnexpectedError = 2, -} - -impl TryFrom for CallbackResult { - // On errors we return the unconverted value - type Error = i32; - - fn try_from(value: i32) -> Result { - match value { - 0 => Ok(Self::Success), - 1 => Ok(Self::Error), - 2 => Ok(Self::UnexpectedError), - n => Err(n), - } - } -} - -/// Struct to hold a foreign callback. -pub struct ForeignCallbackInternals { - callback_cell: ForeignCallbackCell, -} - -impl ForeignCallbackInternals { - pub const fn new() -> Self { - ForeignCallbackInternals { - callback_cell: ForeignCallbackCell::new(), - } - } - - pub fn set_callback(&self, callback: ForeignCallback) { - self.callback_cell.set(callback); - } - - /// Invoke a callback interface method on the foreign side and return the result - pub fn invoke_callback(&self, handle: u64, method: u32, args: RustBuffer) -> R - where - R: LiftReturn, - { - let mut ret_rbuf = RustBuffer::new(); - let callback = self.callback_cell.get(); - let raw_result = unsafe { - callback( - handle, - method, - args.data_pointer(), - args.len() as i32, - &mut ret_rbuf, - ) - }; - let result = CallbackResult::try_from(raw_result) - .unwrap_or_else(|code| panic!("Callback failed with unexpected return code: {code}")); - match result { - CallbackResult::Success => R::lift_callback_return(ret_rbuf), - CallbackResult::Error => R::lift_callback_error(ret_rbuf), - CallbackResult::UnexpectedError => { - let reason = if !ret_rbuf.is_empty() { - match >::try_lift(ret_rbuf) { - Ok(s) => s, - Err(e) => { - log::error!("{{ trait_name }} Error reading ret_buf: {e}"); - String::from("[Error reading reason]") - } - } - } else { - RustBuffer::destroy(ret_rbuf); - String::from("[Unknown Reason]") - }; - R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason }) - } - } - } -} - /// Used when internal/unexpected error happened when calling a foreign callback, for example when /// a unknown exception is raised /// @@ -216,8 +115,10 @@ pub struct UnexpectedUniFFICallbackError { } impl UnexpectedUniFFICallbackError { - pub fn from_reason(reason: String) -> Self { - Self { reason } + pub fn new(reason: impl fmt::Display) -> Self { + Self { + reason: reason.to_string(), + } } } diff --git a/uniffi_core/src/ffi/foreigncallbacks.rs b/uniffi_core/src/ffi/foreigncallbacks.rs index ac2463cd8e..48d0ea11be 100644 --- a/uniffi_core/src/ffi/foreigncallbacks.rs +++ b/uniffi_core/src/ffi/foreigncallbacks.rs @@ -8,34 +8,37 @@ //! code loads the exported library. For each callback type, we also define a "cell" type for //! storing the callback. -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + ptr::{null_mut, NonNull}, + sync::atomic::{AtomicPtr, AtomicUsize, Ordering}, +}; -use crate::{ForeignExecutorHandle, RustBuffer, RustTaskCallback}; +use crate::{ForeignExecutorHandle, RustTaskCallback}; -/// ForeignCallback is the Rust representation of a foreign language function. -/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface, -/// at library start up time. -/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language. -/// -/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object -/// that implements the callback interface/trait. -/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from -/// the IDL. The list is 1 indexed. Note that the list of methods is generated by UniFFI from the IDL and used in all -/// bindings, so we can rely on the method list being stable within the same run of UniFFI. -/// * `args_data` and `args_len` represents a serialized buffer of arguments to the function. The scaffolding code -/// writes the callback arguments to this buffer, in order, using `FfiConverter.write()`. The bindings code reads the -/// arguments from the buffer and passes them to the user's callback. -/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a -/// buffer to write the result into. -/// * Callbacks return one of the `CallbackResult` values -/// Note: The output buffer might still contain 0 bytes of data. -pub type ForeignCallback = unsafe extern "C" fn( - handle: u64, - method: u32, - args_data: *const u8, - args_len: i32, - buf_ptr: *mut RustBuffer, -) -> i32; +// Cell type that stores any NonNull +#[doc(hidden)] +pub struct UniffiForeignPointerCell(AtomicPtr); + +impl UniffiForeignPointerCell { + pub const fn new() -> Self { + Self(AtomicPtr::new(null_mut())) + } + + pub fn set(&self, callback: NonNull) { + self.0.store(callback.as_ptr(), Ordering::Relaxed); + } + + pub fn get(&self) -> &mut T { + unsafe { + NonNull::new(self.0.load(Ordering::Relaxed)) + .expect("Foreign pointer not set. This is likely a uniffi bug.") + .as_mut() + } + } +} + +unsafe impl Send for UniffiForeignPointerCell {} +unsafe impl Sync for UniffiForeignPointerCell {} /// Callback to schedule a Rust call with a `ForeignExecutor`. The bindings code registers exactly /// one of these with the Rust code. @@ -56,9 +59,6 @@ pub type ForeignExecutorCallback = extern "C" fn( task_data: *const (), ) -> i8; -/// Store a [ForeignCallback] pointer -pub(crate) struct ForeignCallbackCell(AtomicUsize); - /// Store a [ForeignExecutorCallback] pointer pub(crate) struct ForeignExecutorCallbackCell(AtomicUsize); @@ -99,5 +99,4 @@ macro_rules! impl_foreign_callback_cell { }; } -impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell); impl_foreign_callback_cell!(ForeignExecutorCallback, ForeignExecutorCallbackCell); diff --git a/uniffi_core/src/ffi/rustcalls.rs b/uniffi_core/src/ffi/rustcalls.rs index 53265393c0..9f801c35a4 100644 --- a/uniffi_core/src/ffi/rustcalls.rs +++ b/uniffi_core/src/ffi/rustcalls.rs @@ -56,6 +56,13 @@ pub struct RustCallStatus { } impl RustCallStatus { + pub fn new() -> Self { + Self { + code: RustCallStatusCode::Success, + error_buf: MaybeUninit::new(RustBuffer::new()), + } + } + pub fn cancelled() -> Self { Self { code: RustCallStatusCode::Cancelled, diff --git a/uniffi_core/src/ffi_converter_impls.rs b/uniffi_core/src/ffi_converter_impls.rs index af18f3873b..be6924461b 100644 --- a/uniffi_core/src/ffi_converter_impls.rs +++ b/uniffi_core/src/ffi_converter_impls.rs @@ -498,7 +498,11 @@ unsafe impl LowerReturn for () { } unsafe impl LiftReturn for () { - fn lift_callback_return(_buf: RustBuffer) -> Self {} + type ReturnType = (); + + fn try_lift_successful_return(_: ()) -> Result { + Ok(()) + } const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); } @@ -535,13 +539,15 @@ where unsafe impl LiftReturn for Result where R: LiftReturn, - E: Lift + ConvertError, + E: Lift + ConvertError, { - fn lift_callback_return(buf: RustBuffer) -> Self { - Ok(R::lift_callback_return(buf)) + type ReturnType = R::ReturnType; + + fn try_lift_successful_return(v: R::ReturnType) -> Result { + R::try_lift_successful_return(v).map(Ok) } - fn lift_callback_error(buf: RustBuffer) -> Self { + fn lift_error(buf: RustBuffer) -> Self { match E::try_lift_from_rust_buffer(buf) { Ok(lifted_error) => Err(lifted_error), Err(anyhow_error) => { diff --git a/uniffi_core/src/ffi_converter_traits.rs b/uniffi_core/src/ffi_converter_traits.rs index 3b5914e32f..987a47c414 100644 --- a/uniffi_core/src/ffi_converter_traits.rs +++ b/uniffi_core/src/ffi_converter_traits.rs @@ -51,7 +51,10 @@ use std::{borrow::Borrow, sync::Arc}; use anyhow::bail; use bytes::Buf; -use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError}; +use crate::{ + FfiDefault, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, + UnexpectedUniFFICallbackError, +}; /// Generalized FFI conversions /// @@ -302,14 +305,41 @@ pub unsafe trait LowerReturn: Sized { /// These traits should not be used directly, only in generated code, and the generated code should /// have fixture tests to test that everything works correctly together. pub unsafe trait LiftReturn: Sized { - /// Lift a Rust value for a callback interface method result - fn lift_callback_return(buf: RustBuffer) -> Self; + /// FFI return type for trait interfaces + type ReturnType; + + /// Lift a successfully returned value from a trait interface + fn try_lift_successful_return(v: Self::ReturnType) -> Result; + + /// Lift a foreign returned value from a trait interface + /// + /// When we call a foreign-implemented trait interface method, we pass a &mut RustCallStatus + /// and get [Self::ReturnType] returned. This method takes both of those and lifts `Self` from + /// it. + fn lift_foreign_return(ffi_return: Self::ReturnType, call_status: RustCallStatus) -> Self { + match call_status.code { + RustCallStatusCode::Success => Self::try_lift_successful_return(ffi_return) + .unwrap_or_else(|e| { + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) + }), + RustCallStatusCode::Error => { + Self::lift_error(unsafe { call_status.error_buf.assume_init() }) + } + _ => { + let e = >::try_lift(unsafe { + call_status.error_buf.assume_init() + }) + .unwrap_or_else(|e| format!("(Error lifting message: {e}")); + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) + } + } + } /// Lift a Rust value for a callback interface method error result /// /// This is called for "expected errors" -- the callback method returns a Result<> type and the /// foreign code throws an exception that corresponds to the error type. - fn lift_callback_error(_buf: RustBuffer) -> Self { + fn lift_error(_buf: RustBuffer) -> Self { panic!("Callback interface method returned unexpected error") } @@ -439,9 +469,10 @@ macro_rules! derive_ffi_traits { (impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { unsafe impl $(<$($generic),*>)* $crate::LiftReturn<$ut> for $ty $(where $($where)*)* { - fn lift_callback_return(buf: $crate::RustBuffer) -> Self { - >::try_lift_from_rust_buffer(buf) - .expect("Error reading callback interface result") + type ReturnType = >::FfiType; + + fn try_lift_successful_return(v: Self::ReturnType) -> $crate::Result { + >::try_lift(v) } const TYPE_ID_META: $crate::MetadataBuffer = >::TYPE_ID_META; diff --git a/uniffi_macros/src/export/callback_interface.rs b/uniffi_macros/src/export/callback_interface.rs index 81258f0bb7..46e560de3f 100644 --- a/uniffi_macros/src/export/callback_interface.rs +++ b/uniffi_macros/src/export/callback_interface.rs @@ -8,10 +8,16 @@ use crate::{ util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}, }; use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{format_ident, quote}; use std::iter; use syn::Ident; +/// Generate a trait impl that calls foreign callbacks +/// +/// This generates: +/// * A `repr(C)` VTable struct where each field is the FFI function for the trait method. +/// * A FFI function for foreign code to set their VTable for the interface +/// * An implementation of the trait using that VTable pub(super) fn trait_impl( mod_path: &str, trait_ident: &Ident, @@ -19,30 +25,51 @@ pub(super) fn trait_impl( ) -> syn::Result { let trait_name = ident_to_string(trait_ident); let trait_impl_ident = trait_impl_ident(&trait_name); - let internals_ident = internals_ident(&trait_name); + let vtable_type = format_ident!("UniFfiTraitVtable{trait_name}"); + let vtable_cell = format_ident!("UNIFFI_TRAIT_CELL_{}", trait_name.to_uppercase()); let init_ident = Ident::new( - &uniffi_meta::init_callback_fn_symbol_name(mod_path, &trait_name), + &uniffi_meta::init_callback_vtable_fn_symbol_name(mod_path, &trait_name), Span::call_site(), ); - - let trait_impl_methods = items - .iter() + let methods = items + .into_iter() .map(|item| match item { - ImplItem::Method(sig) => gen_method_impl(sig, &internals_ident), - _ => unreachable!("traits have no constructors"), + ImplItem::Constructor(sig) => Err(syn::Error::new( + sig.span, + "Constructors not allowed in trait interfaces", + )), + ImplItem::Method(sig) => Ok(sig), }) - .collect::>()?; + .collect::>>()?; + + let vtable_fields = methods.iter() + .map(|sig| { + let ident = &sig.ident; + let params = sig.scaffolding_params(); + let lower_return = sig.lower_return_impl(); + quote! { + #ident: extern "C" fn(handle: u64, #(#params,)* &mut #lower_return::ReturnType, &mut ::uniffi::RustCallStatus), + } + }); + + let trait_impl_methods = methods + .iter() + .map(|sig| gen_method_impl(sig, &vtable_cell)) + .collect::>>()?; + Ok(quote! { - #[doc(hidden)] - static #internals_ident: ::uniffi::ForeignCallbackInternals = ::uniffi::ForeignCallbackInternals::new(); + struct #vtable_type { + #(#vtable_fields)* + uniffi_dec_ref: extern "C" fn(handle: u64), + } + + static #vtable_cell: ::uniffi::UniffiForeignPointerCell::<#vtable_type> = ::uniffi::UniffiForeignPointerCell::<#vtable_type>::new(); - #[doc(hidden)] #[no_mangle] - pub extern "C" fn #init_ident(callback: ::uniffi::ForeignCallback) { - #internals_ident.set_callback(callback); + extern "C" fn #init_ident(vtable: ::std::ptr::NonNull<#vtable_type>) { + #vtable_cell.set(vtable); } - #[doc(hidden)] #[derive(Debug)] struct #trait_impl_ident { handle: u64, @@ -54,19 +81,21 @@ pub(super) fn trait_impl( } } + // Implement Send + Sync to declare that we handle the pointer correctly and you can safely + // share this type between threads. + unsafe impl Send for #trait_impl_ident { } + unsafe impl Sync for #trait_impl_ident { } + + impl #trait_ident for #trait_impl_ident { + #(#trait_impl_methods)* + } + impl ::std::ops::Drop for #trait_impl_ident { fn drop(&mut self) { - #internals_ident.invoke_callback::<(), crate::UniFfiTag>( - self.handle, uniffi::IDX_CALLBACK_FREE, Default::default() - ) + let vtable = #vtable_cell.get(); + (vtable.uniffi_dec_ref)(self.handle); } } - - ::uniffi::deps::static_assertions::assert_impl_all!(#trait_impl_ident: Send); - - impl #trait_ident for #trait_impl_ident { - #trait_impl_methods - } }) } @@ -77,16 +106,6 @@ pub fn trait_impl_ident(trait_name: &str) -> Ident { ) } -pub fn internals_ident(trait_name: &str) -> Ident { - Ident::new( - &format!( - "UNIFFI_FOREIGN_CALLBACK_INTERNALS_{}", - trait_name.to_ascii_uppercase() - ), - Span::call_site(), - ) -} - pub fn ffi_converter_callback_interface_impl( trait_ident: &Ident, trait_impl_ident: &Ident, @@ -131,49 +150,55 @@ pub fn ffi_converter_callback_interface_impl( } } -fn gen_method_impl(sig: &FnSignature, internals_ident: &Ident) -> syn::Result { +/// Generate a single method for for [foreign_impl]. This implements a trait method by invoking a +/// foreign-supplied callback. +fn gen_method_impl(sig: &FnSignature, vtable_cell: &Ident) -> syn::Result { let FnSignature { ident, return_ty, kind, receiver, + name, + span, .. } = sig; - let index = match kind { - // Note: the callback index is 1-based, since 0 is reserved for the free function - FnKind::TraitMethod { index, .. } => index + 1, - k => { - return Err(syn::Error::new( - sig.span, - format!( - "Internal UniFFI error: Unexpected function kind for callback interface {k:?}" - ), - )); - } - }; + + if !matches!(kind, FnKind::TraitMethod { .. }) { + return Err(syn::Error::new( + *span, + format!( + "Internal UniFFI error: Unexpected function kind for callback interface {name}: {kind:?}", + ), + )); + } let self_param = match receiver { + Some(ReceiverArg::Ref) => quote! { &self }, + Some(ReceiverArg::Arc) => quote! { self: Arc }, None => { return Err(syn::Error::new( - sig.span, + *span, "callback interface methods must take &self as their first argument", )); } - Some(ReceiverArg::Ref) => quote! { &self }, - Some(ReceiverArg::Arc) => quote! { self: Arc }, }; + let params = sig.params(); - let buf_ident = Ident::new("uniffi_args_buf", Span::call_site()); - let write_exprs = sig.write_exprs(&buf_ident); + let lower_exprs = sig.args.iter().map(|a| { + let lower_impl = a.lower_impl(); + let ident = &a.ident; + quote! { #lower_impl::lower(#ident) } + }); + + let lift_return = sig.lift_return_impl(); Ok(quote! { fn #ident(#self_param, #(#params),*) -> #return_ty { - #[allow(unused_mut)] - let mut #buf_ident = ::std::vec::Vec::new(); - #(#write_exprs;)* - let uniffi_args_rbuf = uniffi::RustBuffer::from_vec(#buf_ident); - - #internals_ident.invoke_callback::<#return_ty, crate::UniFfiTag>(self.handle, #index, uniffi_args_rbuf) + let vtable = #vtable_cell.get(); + let mut uniffi_call_status = ::uniffi::RustCallStatus::new(); + let mut return_value: #lift_return::ReturnType = ::uniffi::FfiDefault::ffi_default(); + (vtable.#ident)(self.handle, #(#lower_exprs,)* &mut return_value, &mut uniffi_call_status); + #lift_return::lift_foreign_return(return_value, uniffi_call_status) } }) } diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index d00d8403bd..936b68198e 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -231,7 +231,7 @@ pub(super) fn gen_ffi_function( let ffi_ident = sig.scaffolding_fn_ident()?; let name = &sig.name; - let return_impl = &sig.return_impl(); + let return_impl = &sig.lower_return_impl(); Ok(if !sig.is_async { quote! { diff --git a/uniffi_macros/src/fnsig.rs b/uniffi_macros/src/fnsig.rs index 69b6529d1e..2b448b86f1 100644 --- a/uniffi_macros/src/fnsig.rs +++ b/uniffi_macros/src/fnsig.rs @@ -100,13 +100,20 @@ impl FnSignature { }) } - pub fn return_impl(&self) -> TokenStream { + pub fn lower_return_impl(&self) -> TokenStream { let return_ty = &self.return_ty; quote! { <#return_ty as ::uniffi::LowerReturn> } } + pub fn lift_return_impl(&self) -> TokenStream { + let return_ty = &self.return_ty; + quote! { + <#return_ty as ::uniffi::LiftReturn> + } + } + /// Generate a closure that tries to lift all arguments into a tuple. /// /// The closure moves all scaffolding arguments into itself and returns: @@ -151,14 +158,6 @@ impl FnSignature { quote! { #(#args),* } } - /// Write expressions for each of our arguments - pub fn write_exprs<'a>( - &'a self, - buf_ident: &'a Ident, - ) -> impl Iterator + 'a { - self.args.iter().map(|a| a.write_expr(buf_ident)) - } - /// Parameters expressions for each of our arguments pub fn params(&self) -> impl Iterator + '_ { self.args.iter().map(NamedArg::param) @@ -426,13 +425,6 @@ impl NamedArg { quote! { #ident: #lift_impl::FfiType } } - /// Generate the expression to write the scaffolding parameter for this arg - pub(crate) fn write_expr(&self, buf_ident: &Ident) -> TokenStream { - let ident = &self.ident; - let lower_impl = self.lower_impl(); - quote! { #lower_impl::write(#ident, &mut #buf_ident) } - } - pub(crate) fn arg_metadata(&self) -> TokenStream { let name = &self.name; let lift_impl = self.lift_impl(); diff --git a/uniffi_meta/src/ffi_names.rs b/uniffi_meta/src/ffi_names.rs index 44a5bc3e63..4cbe3e45e2 100644 --- a/uniffi_meta/src/ffi_names.rs +++ b/uniffi_meta/src/ffi_names.rs @@ -40,9 +40,12 @@ pub fn free_fn_symbol_name(namespace: &str, object_name: &str) -> String { } /// FFI symbol name for the `init_callback` function for a callback interface -pub fn init_callback_fn_symbol_name(namespace: &str, callback_interface_name: &str) -> String { +pub fn init_callback_vtable_fn_symbol_name( + namespace: &str, + callback_interface_name: &str, +) -> String { let callback_interface_name = callback_interface_name.to_ascii_lowercase(); - format!("uniffi_{namespace}_fn_init_callback_{callback_interface_name}") + format!("uniffi_{namespace}_fn_init_callback_vtable_{callback_interface_name}") } /// FFI checksum symbol name for a top-level function