diff options
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/interface/callbacks.rs')
-rw-r--r-- | third_party/rust/uniffi_bindgen/src/interface/callbacks.rs | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs new file mode 100644 index 0000000000..a7164cec5b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs @@ -0,0 +1,169 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! # Callback Interface definitions for a `ComponentInterface`. +//! +//! This module converts callback interface definitions from UDL into structures that +//! can be added to a `ComponentInterface`. A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! callback interface Example { +//! string hello(); +//! }; +//! # "##)?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`CallbackInterface`] member being added to the resulting +//! [`ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # callback interface Example { +//! # string hello(); +//! # }; +//! # "##)?; +//! let callback = ci.get_callback_interface_definition("Example").unwrap(); +//! assert_eq!(callback.name(), "Example"); +//! assert_eq!(callback.methods()[0].name(), "hello"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::{bail, Result}; +use uniffi_meta::Checksum; + +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::object::Method; +use super::types::{Type, TypeIterator}; +use super::{APIConverter, ComponentInterface}; + +#[derive(Debug, Clone, Checksum)] +pub struct CallbackInterface { + pub(super) name: String, + pub(super) methods: Vec<Method>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_init_callback: FfiFunction, +} + +impl CallbackInterface { + fn new(name: String) -> CallbackInterface { + CallbackInterface { + name, + methods: Default::default(), + ffi_init_callback: Default::default(), + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn type_(&self) -> Type { + Type::CallbackInterface(self.name.clone()) + } + + pub fn methods(&self) -> Vec<&Method> { + self.methods.iter().collect() + } + + pub fn ffi_init_callback(&self) -> &FfiFunction { + &self.ffi_init_callback + } + + pub(super) fn derive_ffi_funcs(&mut self, ci_prefix: &str) { + self.ffi_init_callback.name = format!("ffi_{ci_prefix}_{}_init_callback", self.name); + self.ffi_init_callback.arguments = vec![FfiArgument { + name: "callback_stub".to_string(), + type_: FfiType::ForeignCallback, + }]; + self.ffi_init_callback.return_type = None; + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.methods.iter().flat_map(Method::iter_types)) + } +} + +impl APIConverter<CallbackInterface> for weedle::CallbackInterfaceDefinition<'_> { + fn convert(&self, ci: &mut ComponentInterface) -> Result<CallbackInterface> { + if self.attributes.is_some() { + bail!("callback interface attributes are not supported yet"); + } + if self.inheritance.is_some() { + bail!("callback interface inheritance is not supported"); + } + let mut object = CallbackInterface::new(self.identifier.0.to_string()); + for member in &self.members.body { + match member { + weedle::interface::InterfaceMember::Operation(t) => { + let mut method: Method = t.convert(ci)?; + method.object_name = object.name.clone(); + object.methods.push(method); + } + _ => bail!( + "no support for callback interface member type {:?} yet", + member + ), + } + } + Ok(object) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_empty_interface() { + const UDL: &str = r#" + namespace test{}; + // Weird, but allowed. + callback interface Testing {}; + "#; + let ci = ComponentInterface::from_webidl(UDL).unwrap(); + assert_eq!(ci.callback_interface_definitions().len(), 1); + assert_eq!( + ci.get_callback_interface_definition("Testing") + .unwrap() + .methods() + .len(), + 0 + ); + } + + #[test] + fn test_multiple_interfaces() { + const UDL: &str = r#" + namespace test{}; + callback interface One { + void one(); + }; + callback interface Two { + u32 two(); + u64 too(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL).unwrap(); + assert_eq!(ci.callback_interface_definitions().len(), 2); + + let callbacks_one = ci.get_callback_interface_definition("One").unwrap(); + assert_eq!(callbacks_one.methods().len(), 1); + assert_eq!(callbacks_one.methods()[0].name(), "one"); + + let callbacks_two = ci.get_callback_interface_definition("Two").unwrap(); + assert_eq!(callbacks_two.methods().len(), 2); + assert_eq!(callbacks_two.methods()[0].name(), "two"); + assert_eq!(callbacks_two.methods()[1].name(), "too"); + } +} |