From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- .../rust/uniffi_bindgen/src/interface/callbacks.rs | 207 ++++++++++++- .../rust/uniffi_bindgen/src/interface/enum_.rs | 224 +++++++++++++- .../rust/uniffi_bindgen/src/interface/ffi.rs | 195 +++++++++++- .../rust/uniffi_bindgen/src/interface/function.rs | 32 ++ .../rust/uniffi_bindgen/src/interface/mod.rs | 342 +++++++++++++-------- .../rust/uniffi_bindgen/src/interface/object.rs | 199 +++++++++++- .../rust/uniffi_bindgen/src/interface/record.rs | 53 ++++ .../rust/uniffi_bindgen/src/interface/universe.rs | 5 +- 8 files changed, 1087 insertions(+), 170 deletions(-) (limited to 'third_party/rust/uniffi_bindgen/src/interface') diff --git a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs index e3bca4f966..f176a7a684 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs @@ -33,9 +33,12 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` +use std::iter; + +use heck::ToUpperCamelCase; use uniffi_meta::Checksum; -use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::ffi::{FfiArgument, FfiCallbackFunction, FfiField, FfiFunction, FfiStruct, FfiType}; use super::object::Method; use super::{AsType, Type, TypeIterator}; @@ -52,18 +55,11 @@ pub struct CallbackInterface { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_init_callback: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option, } impl CallbackInterface { - pub fn new(name: String, module_path: String) -> CallbackInterface { - CallbackInterface { - name, - module_path, - methods: Default::default(), - ffi_init_callback: Default::default(), - } - } - pub fn name(&self) -> &str { &self.name } @@ -77,18 +73,45 @@ impl CallbackInterface { } pub(super) fn derive_ffi_funcs(&mut self) { - self.ffi_init_callback.name = - uniffi_meta::init_callback_fn_symbol_name(&self.module_path, &self.name); - self.ffi_init_callback.arguments = vec![FfiArgument { - name: "callback_stub".to_string(), - type_: FfiType::ForeignCallback, - }]; - self.ffi_init_callback.return_type = None; + 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<'_> { Box::new(self.methods.iter().flat_map(Method::iter_types)) } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + + pub fn has_async_method(&self) -> bool { + self.methods.iter().any(Method::is_async) + } } impl AsType for CallbackInterface { @@ -100,6 +123,139 @@ impl AsType for CallbackInterface { } } +impl TryFrom for CallbackInterface { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::CallbackInterfaceMetadata) -> anyhow::Result { + Ok(Self { + name: meta.name, + module_path: meta.module_path, + methods: Default::default(), + ffi_init_callback: Default::default(), + docstring: meta.docstring.clone(), + }) + } +} + +/// [FfiCallbackFunction] functions for the methods of a callback/trait interface +pub fn ffi_callbacks(trait_name: &str, methods: &[Method]) -> Vec { + methods + .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 { + if !method.is_async() { + 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, + } + } else { + let completion_callback = + ffi_foreign_future_complete(method.return_type().map(FfiType::from)); + 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([ + FfiArgument::new( + "uniffi_future_callback", + FfiType::Callback(completion_callback.name), + ), + FfiArgument::new("uniffi_callback_data", FfiType::UInt64), + FfiArgument::new( + "uniffi_out_return", + FfiType::Struct("ForeignFuture".to_owned()).reference(), + ), + ]) + .collect(), + has_rust_call_status_arg: false, + return_type: None, + } + } +} + +/// Result struct to pass to the completion callback for async methods +pub fn foreign_future_ffi_result_struct(return_ffi_type: Option) -> FfiStruct { + let return_type_name = + FfiType::return_type_name(return_ffi_type.as_ref()).to_upper_camel_case(); + FfiStruct { + name: format!("ForeignFutureStruct{return_type_name}"), + fields: match return_ffi_type { + Some(return_ffi_type) => vec![ + FfiField::new("return_value", return_ffi_type), + FfiField::new("call_status", FfiType::RustCallStatus), + ], + None => vec![ + // In Rust, `return_value` is `()` -- a ZST. + // ZSTs are not valid in `C`, but they also take up 0 space. + // Skip the `return_value` field to make the layout correct. + FfiField::new("call_status", FfiType::RustCallStatus), + ], + }, + } +} + +/// Definition for callback functions to complete an async callback interface method +pub fn ffi_foreign_future_complete(return_ffi_type: Option) -> FfiCallbackFunction { + let return_type_name = + FfiType::return_type_name(return_ffi_type.as_ref()).to_upper_camel_case(); + FfiCallbackFunction { + name: format!("ForeignFutureComplete{return_type_name}"), + arguments: vec![ + FfiArgument::new("callback_data", FfiType::UInt64), + FfiArgument::new( + "result", + FfiType::Struct(format!("ForeignFutureStruct{return_type_name}")), + ), + ], + return_type: None, + has_rust_call_status_arg: false, + } +} + +/// [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 + .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; @@ -146,4 +302,21 @@ mod test { assert_eq!(callbacks_two.methods()[0].name(), "two"); assert_eq!(callbacks_two.methods()[1].name(), "too"); } + + #[test] + fn test_docstring_callback_interface() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + callback interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_callback_interface_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs index 82baf1dd50..a666cc3605 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs @@ -94,7 +94,9 @@ //! //! ``` //! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" -//! # namespace example {}; +//! # namespace example { +//! # [Throws=Example] void func(); +//! # }; //! # [Error] //! # enum Example { //! # "one", @@ -130,7 +132,9 @@ //! //! ``` //! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" -//! # namespace example {}; +//! # namespace example { +//! # [Throws=Example] void func(); +//! # }; //! # [Error] //! # interface Example { //! # one(); @@ -159,7 +163,7 @@ use anyhow::Result; use uniffi_meta::Checksum; use super::record::Field; -use super::{AsType, Type, TypeIterator}; +use super::{AsType, Literal, Type, TypeIterator}; /// Represents an enum with named variants, each of which may have named /// and typed fields. @@ -170,6 +174,7 @@ use super::{AsType, Type, TypeIterator}; pub struct Enum { pub(super) name: String, pub(super) module_path: String, + pub(super) discr_type: Option, pub(super) variants: Vec, // NOTE: `flat` is a misleading name and to make matters worse, has 2 different // meanings depending on the context :( @@ -189,6 +194,9 @@ pub struct Enum { // * For an Enum not used as an error but which has no variants with data, `flat` will be // false when generating the scaffolding but `true` when generating bindings. pub(super) flat: bool, + pub(super) non_exhaustive: bool, + #[checksum_ignore] + pub(super) docstring: Option, } impl Enum { @@ -200,14 +208,61 @@ impl Enum { &self.variants } + // Get the literal value to use for the specified variant's discriminant. + // Follows Rust's rules when mixing specified and unspecified values; please + // file a bug if you find a case where it does not. + // However, it *does not* attempt to handle error cases - either cases where + // a discriminant is not unique, or where a discriminant would overflow the + // repr. The intention is that the Rust compiler itself will fail to build + // in those cases, so by the time this get's run we can be confident these + // error cases can't exist. + pub fn variant_discr(&self, variant_index: usize) -> Result { + if variant_index >= self.variants.len() { + anyhow::bail!("Invalid variant index {variant_index}"); + } + let mut next = 0; + let mut this; + let mut this_lit = Literal::new_uint(0); + for v in self.variants().iter().take(variant_index + 1) { + (this, this_lit) = match v.discr { + None => ( + next, + if (next as i64) < 0 { + Literal::new_int(next as i64) + } else { + Literal::new_uint(next) + }, + ), + Some(Literal::UInt(v, _, _)) => (v, Literal::new_uint(v)), + // in-practice, Literal::Int == a negative number. + Some(Literal::Int(v, _, _)) => (v as u64, Literal::new_int(v)), + _ => anyhow::bail!("Invalid literal type {v:?}"), + }; + next = this.wrapping_add(1); + } + Ok(this_lit) + } + + pub fn variant_discr_type(&self) -> &Option { + &self.discr_type + } + pub fn is_flat(&self) -> bool { self.flat } + pub fn is_non_exhaustive(&self) -> bool { + self.non_exhaustive + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.variants.iter().flat_map(Variant::iter_types)) } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + // Sadly can't use TryFrom due to the 'is_flat' complication. pub fn try_from_meta(meta: uniffi_meta::EnumMetadata, flat: bool) -> Result { // This is messy - error enums are considered "flat" if the user @@ -218,12 +273,15 @@ impl Enum { Ok(Self { name: meta.name, module_path: meta.module_path, + discr_type: meta.discr_type, variants: meta .variants .into_iter() .map(TryInto::try_into) .collect::>()?, flat, + non_exhaustive: meta.non_exhaustive, + docstring: meta.docstring.clone(), }) } } @@ -243,7 +301,10 @@ impl AsType for Enum { #[derive(Debug, Clone, Default, PartialEq, Eq, Checksum)] pub struct Variant { pub(super) name: String, + pub(super) discr: Option, pub(super) fields: Vec, + #[checksum_ignore] + pub(super) docstring: Option, } impl Variant { @@ -259,6 +320,14 @@ impl Variant { !self.fields.is_empty() } + pub fn has_nameless_fields(&self) -> bool { + self.fields.iter().any(|f| f.name.is_empty()) + } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.fields.iter().flat_map(Field::iter_types)) } @@ -270,11 +339,13 @@ impl TryFrom for Variant { fn try_from(meta: uniffi_meta::VariantMetadata) -> Result { Ok(Self { name: meta.name, + discr: meta.discr, fields: meta .fields .into_iter() .map(TryInto::try_into) .collect::>()?, + docstring: meta.docstring.clone(), }) } } @@ -447,7 +518,10 @@ mod test { #[test] fn test_variants() { const UDL: &str = r#" - namespace test{}; + namespace test{ + [Throws=Testing] + void func(); + }; [Error] enum Testing { "one", "two", "three" }; "#; @@ -486,7 +560,10 @@ mod test { #[test] fn test_variant_data() { const UDL: &str = r#" - namespace test{}; + namespace test{ + [Throws=Testing] + void func(); + }; [Error] interface Testing { @@ -564,4 +641,141 @@ mod test { vec!["Normal", "Error"] ); } + + fn variant(val: Option) -> Variant { + Variant { + name: "v".to_string(), + discr: val.map(Literal::new_uint), + fields: vec![], + docstring: None, + } + } + + fn check_discrs(e: &mut Enum, vs: Vec) -> Vec { + e.variants = vs; + (0..e.variants.len()) + .map(|i| e.variant_discr(i).unwrap()) + .map(|l| match l { + Literal::UInt(v, _, _) => v, + _ => unreachable!(), + }) + .collect() + } + + #[test] + fn test_variant_values() { + let mut e = Enum { + module_path: "test".to_string(), + name: "test".to_string(), + discr_type: None, + variants: vec![], + flat: false, + non_exhaustive: false, + docstring: None, + }; + + assert!(e.variant_discr(0).is_err()); + + // single values + assert_eq!(check_discrs(&mut e, vec![variant(None)]), vec![0]); + assert_eq!(check_discrs(&mut e, vec![variant(Some(3))]), vec![3]); + + // no values + assert_eq!( + check_discrs(&mut e, vec![variant(None), variant(None)]), + vec![0, 1] + ); + + // values + assert_eq!( + check_discrs(&mut e, vec![variant(Some(1)), variant(Some(3))]), + vec![1, 3] + ); + + // mixed values + assert_eq!( + check_discrs(&mut e, vec![variant(None), variant(Some(3)), variant(None)]), + vec![0, 3, 4] + ); + + assert_eq!( + check_discrs( + &mut e, + vec![variant(Some(4)), variant(None), variant(Some(1))] + ), + vec![4, 5, 1] + ); + } + + #[test] + fn test_docstring_enum() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + enum Testing { "foo" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_enum_variant() { + const UDL: &str = r#" + namespace test{}; + enum Testing { + /// informative docstring + "foo" + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_associated_enum() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + [Enum] + interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_associated_enum_variant() { + const UDL: &str = r#" + namespace test{}; + [Enum] + interface Testing { + /// informative docstring + testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs index d18aaf8262..b27cb78477 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs @@ -47,20 +47,49 @@ 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 that handles all callbacks on the foreign language side. - ForeignCallback, - /// Pointer-sized opaque handle that represents a foreign executor. Foreign bindings can - /// either use an actual pointer or a usized integer. - ForeignExecutorHandle, - /// Pointer to the callback function that's invoked to schedule calls with a ForeignExecutor - ForeignExecutorCallback, - /// Pointer to a Rust future - RustFutureHandle, - /// Continuation function for a Rust future - RustFutureContinuationCallback, - RustFutureContinuationData, - // TODO: you can imagine a richer structural typesystem here, e.g. `Ref` or something. - // We don't need that yet and it's possible we never will, so it isn't here for now. + /// Pointer to a callback function. The inner value which matches one of the callback + /// definitions in [crate::ComponentInterface::ffi_definitions]. + Callback(String), + /// Pointer to a FFI struct (e.g. a VTable). The inner value matches one of the struct + /// definitions in [crate::ComponentInterface::ffi_definitions]. + Struct(String), + /// Opaque 64-bit handle + /// + /// These are used to pass objects across the FFI. + Handle, + RustCallStatus, + /// Pointer to an FfiType. + Reference(Box), + /// Opaque pointer + VoidPointer, +} + +impl FfiType { + pub fn reference(self) -> FfiType { + FfiType::Reference(Box::new(self)) + } + + /// Unique name for an FFI return type + pub fn return_type_name(return_type: Option<&FfiType>) -> String { + match return_type { + Some(t) => match t { + FfiType::UInt8 => "u8".to_owned(), + FfiType::Int8 => "i8".to_owned(), + FfiType::UInt16 => "u16".to_owned(), + FfiType::Int16 => "i16".to_owned(), + FfiType::UInt32 => "u32".to_owned(), + FfiType::Int32 => "i32".to_owned(), + FfiType::UInt64 => "u64".to_owned(), + FfiType::Int64 => "i64".to_owned(), + FfiType::Float32 => "f32".to_owned(), + FfiType::Float64 => "f64".to_owned(), + FfiType::RustArcPtr(_) => "pointer".to_owned(), + FfiType::RustBuffer(_) => "rust_buffer".to_owned(), + _ => unimplemented!("FFI return type: {t:?}"), + }, + None => "void".to_owned(), + } + } } /// When passing data across the FFI, each `Type` value will be lowered into a corresponding @@ -94,7 +123,6 @@ impl From<&Type> for FfiType { Type::Object { name, .. } => FfiType::RustArcPtr(name.to_owned()), // Callback interfaces are passed as opaque integer handles. Type::CallbackInterface { .. } => FfiType::UInt64, - Type::ForeignExecutor => FfiType::ForeignExecutorHandle, // Other types are serialized into a bytebuffer and deserialized on the other side. Type::Enum { .. } | Type::Record { .. } @@ -107,6 +135,11 @@ impl From<&Type> for FfiType { name, kind: ExternalKind::Interface, .. + } + | Type::External { + name, + kind: ExternalKind::Trait, + .. } => FfiType::RustArcPtr(name.clone()), Type::External { name, @@ -131,6 +164,24 @@ impl From<&&Type> for FfiType { } } +/// An Ffi definition +#[derive(Debug, Clone)] +pub enum FfiDefinition { + Function(FfiFunction), + CallbackFunction(FfiCallbackFunction), + Struct(FfiStruct), +} + +impl FfiDefinition { + pub fn name(&self) -> &str { + match self { + Self::Function(f) => f.name(), + Self::CallbackFunction(f) => f.name(), + Self::Struct(s) => s.name(), + } + } +} + /// Represents an "extern C"-style function that will be part of the FFI. /// /// These can't be declared explicitly in the UDL, but rather, are derived automatically @@ -150,6 +201,19 @@ pub struct FfiFunction { } impl FfiFunction { + pub fn callback_init(module_path: &str, trait_name: &str, vtable_name: String) -> Self { + Self { + name: uniffi_meta::init_callback_vtable_fn_symbol_name(module_path, trait_name), + arguments: vec![FfiArgument { + name: "vtable".to_string(), + type_: FfiType::Struct(vtable_name).reference(), + }], + return_type: None, + has_rust_call_status_arg: false, + ..Self::default() + } + } + pub fn name(&self) -> &str { &self.name } @@ -181,7 +245,7 @@ impl FfiFunction { ) { self.arguments = args.into_iter().collect(); if self.is_async() { - self.return_type = Some(FfiType::RustFutureHandle); + self.return_type = Some(FfiType::Handle); self.has_rust_call_status_arg = false; } else { self.return_type = return_type; @@ -212,14 +276,113 @@ pub struct FfiArgument { } impl FfiArgument { + pub fn new(name: impl Into, type_: FfiType) -> Self { + Self { + name: name.into(), + type_, + } + } + pub fn name(&self) -> &str { &self.name } + pub fn type_(&self) -> FfiType { self.type_.clone() } } +/// Represents an "extern C"-style callback function +/// +/// These are defined in the foreign code and passed to Rust as a function pointer. +#[derive(Debug, Default, Clone)] +pub struct FfiCallbackFunction { + // Name for this function type. This matches the value inside `FfiType::Callback` + pub(super) name: String, + pub(super) arguments: Vec, + pub(super) return_type: Option, + pub(super) has_rust_call_status_arg: bool, +} + +impl FfiCallbackFunction { + pub fn name(&self) -> &str { + &self.name + } + + pub fn arguments(&self) -> Vec<&FfiArgument> { + self.arguments.iter().collect() + } + + pub fn return_type(&self) -> Option<&FfiType> { + self.return_type.as_ref() + } + + pub fn has_rust_call_status_arg(&self) -> bool { + self.has_rust_call_status_arg + } +} + +/// 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 this struct + pub fn name(&self) -> &str { + &self.name + } + + /// Get the fields for this struct + 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() + } +} + +impl From for FfiDefinition { + fn from(value: FfiFunction) -> FfiDefinition { + FfiDefinition::Function(value) + } +} + +impl From for FfiDefinition { + fn from(value: FfiStruct) -> FfiDefinition { + FfiDefinition::Struct(value) + } +} + +impl From for FfiDefinition { + fn from(value: FfiCallbackFunction) -> FfiDefinition { + FfiDefinition::CallbackFunction(value) + } +} + #[cfg(test)] mod test { // There's not really much to test here to be honest, diff --git a/third_party/rust/uniffi_bindgen/src/interface/function.rs b/third_party/rust/uniffi_bindgen/src/interface/function.rs index 2d18288c1c..8effc4c876 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/function.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/function.rs @@ -59,6 +59,8 @@ pub struct Function { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option, pub(super) throws: Option, pub(super) checksum_fn_name: String, // Force a checksum value, or we'll fallback to the trait. @@ -128,6 +130,10 @@ impl Function { .chain(self.return_type.iter().flat_map(Type::iter_types)), ) } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } } impl From for Argument { @@ -163,6 +169,7 @@ impl From for Function { arguments, return_type, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws, checksum_fn_name, checksum: meta.checksum, @@ -242,6 +249,9 @@ pub trait Callable { fn return_type(&self) -> Option; fn throws_type(&self) -> Option; fn is_async(&self) -> bool; + fn takes_self(&self) -> bool { + false + } fn result_type(&self) -> ResultType { ResultType { return_type: self.return_type(), @@ -311,6 +321,10 @@ impl Callable for &T { fn is_async(&self) -> bool { (*self).is_async() } + + fn takes_self(&self) -> bool { + (*self).takes_self() + } } #[cfg(test)] @@ -364,4 +378,22 @@ mod test { ); Ok(()) } + + #[test] + fn test_docstring_function() { + const UDL: &str = r#" + namespace test { + /// informative docstring + void testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_function_definition("testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/mod.rs b/third_party/rust/uniffi_bindgen/src/interface/mod.rs index 8e4df2149b..90a941637a 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/mod.rs @@ -67,7 +67,9 @@ mod record; pub use record::{Field, Record}; pub mod ffi; -pub use ffi::{FfiArgument, FfiFunction, FfiType}; +pub use ffi::{ + FfiArgument, FfiCallbackFunction, FfiDefinition, FfiField, FfiFunction, FfiStruct, FfiType, +}; pub use uniffi_meta::Radix; use uniffi_meta::{ ConstructorMetadata, LiteralMetadata, NamespaceMetadata, ObjectMetadata, TraitMethodMetadata, @@ -139,6 +141,11 @@ impl ComponentInterface { self.types.namespace ); } + + if group.namespace_docstring.is_some() { + self.types.namespace_docstring = group.namespace_docstring.clone(); + } + // Unconditionally add the String type, which is used by the panic handling self.types.add_known_type(&uniffi_meta::Type::String)?; crate::macro_metadata::add_group_to_ci(self, group)?; @@ -153,6 +160,10 @@ impl ComponentInterface { &self.types.namespace.name } + pub fn namespace_docstring(&self) -> Option<&str> { + self.types.namespace_docstring.as_deref() + } + pub fn uniffi_contract_version(&self) -> u32 { // This is set by the scripts in the version-mismatch fixture let force_version = std::env::var("UNIFFI_FORCE_CONTRACT_VERSION"); @@ -204,6 +215,29 @@ impl ComponentInterface { self.objects.iter().find(|o| o.name == name) } + fn callback_interface_callback_definitions( + &self, + ) -> impl IntoIterator + '_ { + self.callback_interfaces + .iter() + .flat_map(|cbi| cbi.ffi_callbacks()) + .chain(self.objects.iter().flat_map(|o| o.ffi_callbacks())) + } + + /// Get the definitions for callback FFI functions + /// + /// These are defined by the foreign code and invoked by Rust. + fn callback_interface_vtable_definitions(&self) -> impl IntoIterator + '_ { + self.callback_interface_definitions() + .iter() + .map(|cbi| cbi.vtable_definition()) + .chain( + self.object_definitions() + .iter() + .flat_map(|o| o.vtable_definition()), + ) + } + /// Get the definitions for every Callback Interface type in the interface. pub fn callback_interface_definitions(&self) -> &[CallbackInterface] { &self.callback_interfaces @@ -215,6 +249,17 @@ impl ComponentInterface { self.callback_interfaces.iter().find(|o| o.name == name) } + /// Get the definitions for every Callback Interface type in the interface. + pub fn has_async_callback_interface_definition(&self) -> bool { + self.callback_interfaces + .iter() + .any(|cbi| cbi.has_async_method()) + || self + .objects + .iter() + .any(|o| o.has_callback_interface() && o.has_async_method()) + } + /// Get the definitions for every Method type in the interface. pub fn iter_callables(&self) -> impl Iterator { // Each of the `as &dyn Callable` casts is a trivial cast, but it seems like the clearest @@ -241,13 +286,19 @@ impl ComponentInterface { let fielded = !e.is_flat(); // For flat errors, we should only generate read() methods if we need them to support // callback interface errors - let used_in_callback_interface = self + let used_in_foreign_interface = self .callback_interface_definitions() .iter() .flat_map(|cb| cb.methods()) + .chain( + self.object_definitions() + .iter() + .filter(|o| o.has_callback_interface()) + .flat_map(|o| o.methods()), + ) .any(|m| m.throws_type() == Some(&e.as_type())); - self.is_name_used_as_error(&e.name) && (fielded || used_in_callback_interface) + self.is_name_used_as_error(&e.name) && (fielded || used_in_foreign_interface) } /// Get details about all `Type::External` types. @@ -304,8 +355,17 @@ impl ComponentInterface { /// This is important to know in language bindings that cannot integrate object types /// tightly with the host GC, and hence need to perform manual destruction of objects. pub fn item_contains_object_references(&self, item: &Type) -> bool { - self.iter_types_in_item(item) - .any(|t| matches!(t, Type::Object { .. })) + // this is surely broken for external records with object refs? + self.iter_types_in_item(item).any(|t| { + matches!( + t, + Type::Object { .. } + | Type::External { + kind: ExternalKind::Interface, + .. + } + ) + }) } /// Check whether the given item contains any (possibly nested) unsigned types @@ -335,6 +395,13 @@ impl ComponentInterface { .any(|t| matches!(t, Type::Map { .. })) } + /// Check whether the interface contains any object types + pub fn contains_object_types(&self) -> bool { + self.types + .iter_known_types() + .any(|t| matches!(t, Type::Object { .. })) + } + // The namespace to use in crate-level FFI function definitions. Not used as the ffi // namespace for types - each type has its own `module_path` which is used for them. fn ffi_namespace(&self) -> &str { @@ -364,7 +431,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "size".to_string(), - type_: FfiType::Int32, + type_: FfiType::UInt64, }], return_type: Some(FfiType::RustBuffer(None)), has_rust_call_status_arg: true, @@ -420,7 +487,7 @@ impl ComponentInterface { }, FfiArgument { name: "additional".to_string(), - type_: FfiType::Int32, + type_: FfiType::UInt64, }, ], return_type: Some(FfiType::RustBuffer(None)), @@ -429,24 +496,6 @@ impl ComponentInterface { } } - /// Builtin FFI function to set the Rust Future continuation callback - pub fn ffi_rust_future_continuation_callback_set(&self) -> FfiFunction { - FfiFunction { - name: format!( - "ffi_{}_rust_future_continuation_callback_set", - self.ffi_namespace() - ), - arguments: vec![FfiArgument { - name: "callback".to_owned(), - type_: FfiType::RustFutureContinuationCallback, - }], - return_type: None, - is_async: false, - has_rust_call_status_arg: false, - is_object_free_function: false, - } - } - /// Builtin FFI function to poll a Rust future. pub fn ffi_rust_future_poll(&self, return_ffi_type: Option) -> FfiFunction { FfiFunction { @@ -455,12 +504,15 @@ impl ComponentInterface { arguments: vec![ FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, + }, + FfiArgument { + name: "callback".to_owned(), + type_: FfiType::Callback("RustFutureContinuationCallback".to_owned()), }, - // Data to pass to the continuation FfiArgument { - name: "uniffi_callback".to_owned(), - type_: FfiType::RustFutureContinuationData, + name: "callback_data".to_owned(), + type_: FfiType::Handle, }, ], return_type: None, @@ -478,7 +530,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: return_ffi_type, has_rust_call_status_arg: true, @@ -493,7 +545,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -508,7 +560,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -518,29 +570,17 @@ impl ComponentInterface { fn rust_future_ffi_fn_name(&self, base_name: &str, return_ffi_type: Option) -> String { let namespace = self.ffi_namespace(); - match return_ffi_type { - Some(t) => match t { - FfiType::UInt8 => format!("ffi_{namespace}_{base_name}_u8"), - FfiType::Int8 => format!("ffi_{namespace}_{base_name}_i8"), - FfiType::UInt16 => format!("ffi_{namespace}_{base_name}_u16"), - FfiType::Int16 => format!("ffi_{namespace}_{base_name}_i16"), - FfiType::UInt32 => format!("ffi_{namespace}_{base_name}_u32"), - FfiType::Int32 => format!("ffi_{namespace}_{base_name}_i32"), - FfiType::UInt64 => format!("ffi_{namespace}_{base_name}_u64"), - FfiType::Int64 => format!("ffi_{namespace}_{base_name}_i64"), - FfiType::Float32 => format!("ffi_{namespace}_{base_name}_f32"), - FfiType::Float64 => format!("ffi_{namespace}_{base_name}_f64"), - FfiType::RustArcPtr(_) => format!("ffi_{namespace}_{base_name}_pointer"), - FfiType::RustBuffer(_) => format!("ffi_{namespace}_{base_name}_rust_buffer"), - _ => unimplemented!("FFI return type: {t:?}"), - }, - None => format!("ffi_{namespace}_{base_name}_void"), - } + let return_type_name = FfiType::return_type_name(return_ffi_type.as_ref()); + format!("ffi_{namespace}_{base_name}_{return_type_name}") } /// Does this interface contain async functions? pub fn has_async_fns(&self) -> bool { self.iter_ffi_function_definitions().any(|f| f.is_async()) + || self + .callback_interfaces + .iter() + .any(CallbackInterface::has_async_method) } /// Iterate over `T` parameters of the `FutureCallback` callbacks in this interface @@ -561,6 +601,73 @@ impl ComponentInterface { unique_results.into_iter() } + /// Iterate over all Ffi definitions + pub fn ffi_definitions(&self) -> impl Iterator + '_ { + // Note: for languages like Python it's important to keep things in dependency order. + // For example some FFI function definitions depend on FFI struct definitions, so the + // function definitions come last. + self.builtin_ffi_definitions() + .into_iter() + .chain( + self.callback_interface_callback_definitions() + .into_iter() + .map(Into::into), + ) + .chain( + self.callback_interface_vtable_definitions() + .into_iter() + .map(Into::into), + ) + .chain(self.iter_ffi_function_definitions().map(Into::into)) + } + + fn builtin_ffi_definitions(&self) -> impl IntoIterator + '_ { + [ + FfiCallbackFunction { + name: "RustFutureContinuationCallback".to_owned(), + arguments: vec![ + FfiArgument::new("data", FfiType::UInt64), + FfiArgument::new("poll_result", FfiType::Int8), + ], + return_type: None, + has_rust_call_status_arg: false, + } + .into(), + FfiCallbackFunction { + name: "ForeignFutureFree".to_owned(), + arguments: vec![FfiArgument::new("handle", FfiType::UInt64)], + return_type: None, + has_rust_call_status_arg: false, + } + .into(), + FfiCallbackFunction { + name: "CallbackInterfaceFree".to_owned(), + arguments: vec![FfiArgument::new("handle", FfiType::UInt64)], + return_type: None, + has_rust_call_status_arg: false, + } + .into(), + FfiStruct { + name: "ForeignFuture".to_owned(), + fields: vec![ + FfiField::new("handle", FfiType::UInt64), + FfiField::new("free", FfiType::Callback("ForeignFutureFree".to_owned())), + ], + } + .into(), + ] + .into_iter() + .chain( + self.all_possible_return_ffi_types() + .flat_map(|return_type| { + [ + callbacks::foreign_future_ffi_result_struct(return_type.clone()).into(), + callbacks::ffi_foreign_future_complete(return_type).into(), + ] + }), + ) + } + /// List the definitions of all FFI functions in the interface. /// /// The set of FFI functions is derived automatically from the set of higher-level types @@ -569,9 +676,8 @@ impl ComponentInterface { self.iter_user_ffi_function_definitions() .cloned() .chain(self.iter_rust_buffer_ffi_function_definitions()) - .chain(self.iter_futures_ffi_function_definitons()) + .chain(self.iter_futures_ffi_function_definitions()) .chain(self.iter_checksum_ffi_functions()) - .chain(self.ffi_foreign_executor_callback_set()) .chain([self.ffi_uniffi_contract_version()]) } @@ -618,9 +724,8 @@ impl ComponentInterface { .into_iter() } - /// List all FFI functions definitions for async functionality. - pub fn iter_futures_ffi_function_definitons(&self) -> impl Iterator + '_ { - let all_possible_return_ffi_types = [ + fn all_possible_return_ffi_types(&self) -> impl Iterator> { + [ Some(FfiType::UInt8), Some(FfiType::Int8), Some(FfiType::UInt16), @@ -631,46 +736,26 @@ impl ComponentInterface { Some(FfiType::Int64), Some(FfiType::Float32), Some(FfiType::Float64), - // RustBuffer and RustArcPtr have an inner field which doesn't affect the rust future - // complete scaffolding function, so we just use a placeholder value here. + // RustBuffer and RustArcPtr have an inner field which we have to fill in with a + // placeholder value. Some(FfiType::RustArcPtr("".to_owned())), Some(FfiType::RustBuffer(None)), None, - ]; - - iter::once(self.ffi_rust_future_continuation_callback_set()).chain( - all_possible_return_ffi_types - .into_iter() - .flat_map(|return_type| { - [ - self.ffi_rust_future_poll(return_type.clone()), - self.ffi_rust_future_cancel(return_type.clone()), - self.ffi_rust_future_free(return_type.clone()), - self.ffi_rust_future_complete(return_type), - ] - }), - ) + ] + .into_iter() } - /// The ffi_foreign_executor_callback_set FFI function - /// - /// We only include this in the FFI if the `ForeignExecutor` type is actually used - pub fn ffi_foreign_executor_callback_set(&self) -> Option { - if self.types.contains(&Type::ForeignExecutor) { - Some(FfiFunction { - name: format!("ffi_{}_foreign_executor_callback_set", self.ffi_namespace()), - arguments: vec![FfiArgument { - name: "callback".into(), - type_: FfiType::ForeignExecutorCallback, - }], - return_type: None, - is_async: false, - has_rust_call_status_arg: false, - is_object_free_function: false, + /// List all FFI functions definitions for async functionality. + pub fn iter_futures_ffi_function_definitions(&self) -> impl Iterator + '_ { + self.all_possible_return_ffi_types() + .flat_map(|return_type| { + [ + self.ffi_rust_future_poll(return_type.clone()), + self.ffi_rust_future_cancel(return_type.clone()), + self.ffi_rust_future_free(return_type.clone()), + self.ffi_rust_future_complete(return_type), + ] }) - } else { - None - } } /// List all API checksums to check @@ -778,6 +863,8 @@ impl ComponentInterface { bail!("Conflicting type definition for \"{}\"", defn.name()); } self.types.add_known_types(defn.iter_types())?; + defn.throws_name() + .map(|n| self.errors.insert(n.to_string())); self.functions.push(defn); Ok(()) @@ -789,6 +876,8 @@ impl ComponentInterface { let defn: Constructor = meta.into(); self.types.add_known_types(defn.iter_types())?; + defn.throws_name() + .map(|n| self.errors.insert(n.to_string())); object.constructors.push(defn); Ok(()) @@ -800,6 +889,9 @@ impl ComponentInterface { .ok_or_else(|| anyhow!("add_method_meta: object {} not found", &method.object_name))?; self.types.add_known_types(method.iter_types())?; + method + .throws_name() + .map(|n| self.errors.insert(n.to_string())); method.object_impl = object.imp; object.methods.push(method); Ok(()) @@ -825,10 +917,6 @@ impl ComponentInterface { Ok(()) } - pub(super) fn note_name_used_as_error(&mut self, name: &str) { - self.errors.insert(name.to_string()); - } - pub fn is_name_used_as_error(&self, name: &str) -> bool { self.errors.contains(name) } @@ -856,6 +944,9 @@ impl ComponentInterface { self.callback_interface_throws_types.insert(error.clone()); } self.types.add_known_types(method.iter_types())?; + method + .throws_name() + .map(|n| self.errors.insert(n.to_string())); cbi.methods.push(method); } else { self.add_method_meta(meta)?; @@ -880,31 +971,6 @@ impl ComponentInterface { bail!("Conflicting type definition for \"{}\"", f.name()); } } - - for ty in self.iter_types() { - match ty { - Type::Object { name, .. } => { - ensure!( - self.objects.iter().any(|o| o.name == *name), - "Object `{name}` has no definition" - ); - } - Type::Record { name, .. } => { - ensure!( - self.records.contains_key(name), - "Record `{name}` has no definition", - ); - } - Type::Enum { name, .. } => { - ensure!( - self.enums.contains_key(name), - "Enum `{name}` has no definition", - ); - } - _ => {} - } - } - Ok(()) } @@ -1047,7 +1113,7 @@ fn throws_name(throws: &Option) -> Option<&str> { // Type has no `name()` method, just `canonical_name()` which isn't what we want. match throws { None => None, - Some(Type::Enum { name, .. }) => Some(name), + Some(Type::Enum { name, .. }) | Some(Type::Object { name, .. }) => Some(name), _ => panic!("unknown throw type: {throws:?}"), } } @@ -1089,35 +1155,50 @@ mod test { let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err(); assert_eq!( err.to_string(), - "Mismatching definition for enum `Testing`!\nexisting definition: Enum { + "Mismatching definition for enum `Testing`! +existing definition: Enum { name: \"Testing\", module_path: \"crate_name\", + discr_type: None, variants: [ Variant { name: \"one\", + discr: None, fields: [], + docstring: None, }, Variant { name: \"two\", + discr: None, fields: [], + docstring: None, }, ], flat: true, + non_exhaustive: false, + docstring: None, }, new definition: Enum { name: \"Testing\", module_path: \"crate_name\", + discr_type: None, variants: [ Variant { name: \"three\", + discr: None, fields: [], + docstring: None, }, Variant { name: \"four\", + discr: None, fields: [], + docstring: None, }, ], flat: true, + non_exhaustive: false, + docstring: None, }", ); @@ -1231,4 +1312,25 @@ new definition: Enum { imp: ObjectImpl::Struct, })); } + + #[test] + fn test_docstring_namespace() { + const UDL: &str = r#" + /// informative docstring + namespace test{}; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.namespace_docstring().unwrap(), "informative docstring"); + } + + #[test] + fn test_multiline_docstring() { + const UDL: &str = r#" + /// informative + /// docstring + namespace test{}; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.namespace_docstring().unwrap(), "informative\ndocstring"); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/object.rs b/third_party/rust/uniffi_bindgen/src/interface/object.rs index 942032b3c6..2b86e54a45 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/object.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/object.rs @@ -57,12 +57,11 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` -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}; @@ -92,14 +91,24 @@ pub struct Object { // a regular method (albeit with a generated name) // XXX - this should really be a HashSet, but not enough transient types support hash to make it worthwhile now. pub(super) uniffi_traits: Vec, - // We don't include the FfiFunc in the hash calculation, because: + // We don't include the FfiFuncs 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. + + // FFI function to clone a pointer for this object + #[checksum_ignore] + pub(super) ffi_func_clone: FfiFunction, + // FFI function to free a pointer for this object #[checksum_ignore] pub(super) ffi_func_free: FfiFunction, + // Ffi function to initialize the foreign callback for trait interfaces + #[checksum_ignore] + pub(super) ffi_init_callback: Option, + #[checksum_ignore] + pub(super) docstring: Option, } impl Object { @@ -118,6 +127,18 @@ impl Object { &self.imp } + pub fn is_trait_interface(&self) -> bool { + self.imp.is_trait_interface() + } + + pub fn has_callback_interface(&self) -> bool { + self.imp.has_callback_interface() + } + + pub fn has_async_method(&self) -> bool { + self.methods.iter().any(Method::is_async) + } + pub fn constructors(&self) -> Vec<&Constructor> { self.constructors.iter().collect() } @@ -151,12 +172,28 @@ impl Object { self.uniffi_traits.iter().collect() } + pub fn ffi_object_clone(&self) -> &FfiFunction { + &self.ffi_func_clone + } + pub fn ffi_object_free(&self) -> &FfiFunction { &self.ffi_func_free } + pub fn ffi_init_callback(&self) -> &FfiFunction { + self.ffi_init_callback + .as_ref() + .unwrap_or_else(|| panic!("No ffi_init_callback set for {}", &self.name)) + } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_ffi_function_definitions(&self) -> impl Iterator { - iter::once(&self.ffi_func_free) + [&self.ffi_func_clone, &self.ffi_func_free] + .into_iter() + .chain(&self.ffi_init_callback) .chain(self.constructors.iter().map(|f| &f.ffi_func)) .chain(self.methods.iter().map(|f| &f.ffi_func)) .chain( @@ -173,13 +210,26 @@ impl Object { } pub fn derive_ffi_funcs(&mut self) -> Result<()> { + assert!(!self.ffi_func_clone.name().is_empty()); assert!(!self.ffi_func_free.name().is_empty()); + self.ffi_func_clone.arguments = vec![FfiArgument { + name: "ptr".to_string(), + type_: FfiType::RustArcPtr(self.name.to_string()), + }]; + self.ffi_func_clone.return_type = Some(FfiType::RustArcPtr(self.name.to_string())); self.ffi_func_free.arguments = vec![FfiArgument { name: "ptr".to_string(), type_: FfiType::RustArcPtr(self.name.to_string()), }]; self.ffi_func_free.return_type = None; self.ffi_func_free.is_object_free_function = true; + if self.has_callback_interface() { + 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() { cons.derive_ffi_func(); @@ -194,6 +244,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 @@ -218,6 +303,7 @@ impl AsType for Object { impl From for Object { fn from(meta: uniffi_meta::ObjectMetadata) -> Self { + let ffi_clone_name = meta.clone_ffi_symbol_name(); let ffi_free_name = meta.free_ffi_symbol_name(); Object { module_path: meta.module_path, @@ -226,10 +312,16 @@ impl From for Object { constructors: Default::default(), methods: Default::default(), uniffi_traits: Default::default(), + ffi_func_clone: FfiFunction { + name: ffi_clone_name, + ..Default::default() + }, ffi_func_free: FfiFunction { name: ffi_free_name, ..Default::default() }, + ffi_init_callback: None, + docstring: meta.docstring.clone(), } } } @@ -263,6 +355,7 @@ pub struct Constructor { pub(super) name: String, pub(super) object_name: String, pub(super) object_module_path: String, + pub(super) is_async: bool, pub(super) arguments: Vec, // We don't include the FFIFunc in the hash calculation, because: // - it is entirely determined by the other fields, @@ -272,6 +365,8 @@ pub struct Constructor { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option, pub(super) throws: Option, pub(super) checksum_fn_name: String, // Force a checksum value, or we'll fallback to the trait. @@ -316,14 +411,20 @@ impl Constructor { self.throws.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn is_primary_constructor(&self) -> bool { self.name == "new" } fn derive_ffi_func(&mut self) { assert!(!self.ffi_func.name().is_empty()); - self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect(); - self.ffi_func.return_type = Some(FfiType::RustArcPtr(self.object_name.clone())); + self.ffi_func.init( + Some(FfiType::RustArcPtr(self.object_name.clone())), + self.arguments.iter().map(Into::into), + ); } pub fn iter_types(&self) -> TypeIterator<'_> { @@ -339,14 +440,17 @@ impl From for Constructor { let ffi_func = FfiFunction { name: ffi_name, + is_async: meta.is_async, ..FfiFunction::default() }; Self { name: meta.name, object_name: meta.self_name, + is_async: meta.is_async, object_module_path: meta.module_path, arguments, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), checksum_fn_name, checksum: meta.checksum, @@ -375,6 +479,8 @@ pub struct Method { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option, pub(super) throws: Option, pub(super) takes_self_by_arc: bool, pub(super) checksum_fn_name: String, @@ -445,6 +551,10 @@ impl Method { self.throws.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn takes_self_by_arc(&self) -> bool { self.takes_self_by_arc } @@ -466,6 +576,11 @@ impl Method { .chain(self.return_type.iter().flat_map(Type::iter_types)), ) } + + /// For async callback interface methods, the FFI struct to pass to the completion function. + pub fn foreign_future_ffi_result_struct(&self) -> FfiStruct { + callbacks::foreign_future_ffi_result_struct(self.return_type.as_ref().map(FfiType::from)) + } } impl From for Method { @@ -491,6 +606,7 @@ impl From for Method { arguments, return_type, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), takes_self_by_arc: meta.takes_self_by_arc, checksum_fn_name, @@ -503,19 +619,22 @@ impl From for Method { fn from(meta: uniffi_meta::TraitMethodMetadata) -> Self { let ffi_name = meta.ffi_symbol_name(); let checksum_fn_name = meta.checksum_symbol_name(); + let is_async = meta.is_async; let return_type = meta.return_type.map(Into::into); let arguments = meta.inputs.into_iter().map(Into::into).collect(); let ffi_func = FfiFunction { name: ffi_name, + is_async, ..FfiFunction::default() }; Self { name: meta.name, object_name: meta.trait_name, object_module_path: meta.module_path, - is_async: false, + is_async, arguments, return_type, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), takes_self_by_arc: meta.takes_self_by_arc, checksum_fn_name, @@ -583,7 +702,7 @@ impl Callable for Constructor { } fn is_async(&self) -> bool { - false + self.is_async } } @@ -603,6 +722,10 @@ impl Callable for Method { fn is_async(&self) -> bool { self.is_async } + + fn takes_self(&self) -> bool { + true + } } #[cfg(test)] @@ -770,4 +893,62 @@ mod test { "Trait interfaces can not have constructors: \"new\"" ); } + + #[test] + fn test_docstring_object() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_constructor() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + /// informative docstring + constructor(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .primary_constructor() + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_method() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + /// informative docstring + void testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .get_method("testing") + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/record.rs b/third_party/rust/uniffi_bindgen/src/interface/record.rs index 17d3774a49..e9a6004189 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/record.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/record.rs @@ -60,6 +60,8 @@ pub struct Record { pub(super) name: String, pub(super) module_path: String, pub(super) fields: Vec, + #[checksum_ignore] + pub(super) docstring: Option, } impl Record { @@ -71,9 +73,17 @@ impl Record { &self.fields } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.fields.iter().flat_map(Field::iter_types)) } + + pub fn has_fields(&self) -> bool { + !self.fields.is_empty() + } } impl AsType for Record { @@ -97,6 +107,7 @@ impl TryFrom for Record { .into_iter() .map(TryInto::try_into) .collect::>()?, + docstring: meta.docstring.clone(), }) } } @@ -107,6 +118,8 @@ pub struct Field { pub(super) name: String, pub(super) type_: Type, pub(super) default: Option, + #[checksum_ignore] + pub(super) docstring: Option, } impl Field { @@ -118,6 +131,10 @@ impl Field { self.default.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { self.type_.iter_types() } @@ -140,6 +157,7 @@ impl TryFrom for Field { name, type_, default, + docstring: meta.docstring.clone(), }) } } @@ -227,4 +245,39 @@ mod test { .iter_types() .any(|t| matches!(t, Type::Record { name, .. } if name == "Testing"))); } + + #[test] + fn test_docstring_record() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + dictionary Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_record_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_record_field() { + const UDL: &str = r#" + namespace test{}; + dictionary Testing { + /// informative docstring + i32 testing; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_record_definition("Testing").unwrap().fields()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/universe.rs b/third_party/rust/uniffi_bindgen/src/interface/universe.rs index e69d86e44f..70bc61f8a9 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/universe.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/universe.rs @@ -25,6 +25,7 @@ pub use uniffi_meta::{AsType, ExternalKind, NamespaceMetadata, ObjectImpl, Type, pub(crate) struct TypeUniverse { /// The unique prefixes that we'll use for namespacing when exposing this component's API. pub namespace: NamespaceMetadata, + pub namespace_docstring: Option, // Named type definitions (including aliases). type_definitions: HashMap, @@ -83,9 +84,6 @@ impl TypeUniverse { Type::Bytes => self.add_type_definition("bytes", type_)?, Type::Timestamp => self.add_type_definition("timestamp", type_)?, Type::Duration => self.add_type_definition("duration", type_)?, - Type::ForeignExecutor => { - self.add_type_definition("ForeignExecutor", type_)?; - } Type::Object { name, .. } | Type::Record { name, .. } | Type::Enum { name, .. } @@ -118,6 +116,7 @@ impl TypeUniverse { Ok(()) } + #[cfg(test)] /// Check if a [Type] is present pub fn contains(&self, type_: &Type) -> bool { self.all_known_types.contains(type_) -- cgit v1.2.3