summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/interface
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/callbacks.rs207
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/enum_.rs224
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/ffi.rs195
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/function.rs32
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/mod.rs342
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/object.rs199
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/record.rs53
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/universe.rs5
8 files changed, 1087 insertions, 170 deletions
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<String>,
}
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<FfiCallbackFunction> {
+ 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<uniffi_meta::CallbackInterfaceMetadata> for CallbackInterface {
+ type Error = anyhow::Error;
+
+ fn try_from(meta: uniffi_meta::CallbackInterfaceMetadata) -> anyhow::Result<Self> {
+ 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<FfiCallbackFunction> {
+ 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<FfiType>) -> 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<FfiType>) -> 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<Type>,
pub(super) variants: Vec<Variant>,
// 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<String>,
}
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<Literal> {
+ 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<Type> {
+ &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<Self> {
// 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::<Result<_>>()?,
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<Literal>,
pub(super) fields: Vec<Field>,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
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<uniffi_meta::VariantMetadata> for Variant {
fn try_from(meta: uniffi_meta::VariantMetadata) -> Result<Self> {
Ok(Self {
name: meta.name,
+ discr: meta.discr,
fields: meta
.fields
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_>>()?,
+ 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<u64>) -> Variant {
+ Variant {
+ name: "v".to_string(),
+ discr: val.map(Literal::new_uint),
+ fields: vec![],
+ docstring: None,
+ }
+ }
+
+ fn check_discrs(e: &mut Enum, vs: Vec<Variant>) -> Vec<u64> {
+ 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<String>` 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<FfiType>),
+ /// 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<String>, 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<FfiArgument>,
+ pub(super) return_type: Option<FfiType>,
+ 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<FfiField>,
+}
+
+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<String>, 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<FfiFunction> for FfiDefinition {
+ fn from(value: FfiFunction) -> FfiDefinition {
+ FfiDefinition::Function(value)
+ }
+}
+
+impl From<FfiStruct> for FfiDefinition {
+ fn from(value: FfiStruct) -> FfiDefinition {
+ FfiDefinition::Struct(value)
+ }
+}
+
+impl From<FfiCallbackFunction> 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<String>,
pub(super) throws: Option<Type>,
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<uniffi_meta::FnParamMetadata> for Argument {
@@ -163,6 +169,7 @@ impl From<uniffi_meta::FnMetadata> 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<Type>;
fn throws_type(&self) -> Option<Type>;
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<T: Callable> 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<Item = FfiCallbackFunction> + '_ {
+ 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<Item = FfiStruct> + '_ {
+ 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<Item = &dyn Callable> {
// 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<FfiType>) -> 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<FfiType>) -> 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<T>` 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<Item = FfiDefinition> + '_ {
+ // 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<Item = FfiDefinition> + '_ {
+ [
+ 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<Item = FfiFunction> + '_ {
- let all_possible_return_ffi_types = [
+ fn all_possible_return_ffi_types(&self) -> impl Iterator<Item = Option<FfiType>> {
+ [
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<FfiFunction> {
- 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<Item = FfiFunction> + '_ {
+ 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<Type>) -> 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<UniffiTrait>,
- // 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<FfiFunction>,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
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<Item = &FfiFunction> {
- 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<FfiCallbackFunction> {
+ 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<FfiType> {
+ 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<FfiStruct> {
+ 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<uniffi_meta::ObjectMetadata> 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<uniffi_meta::ObjectMetadata> 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<Argument>,
// 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<String>,
pub(super) throws: Option<Type>,
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<uniffi_meta::ConstructorMetadata> 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<String>,
pub(super) throws: Option<Type>,
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<uniffi_meta::MethodMetadata> for Method {
@@ -491,6 +606,7 @@ impl From<uniffi_meta::MethodMetadata> 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<uniffi_meta::TraitMethodMetadata> 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<Field>,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
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<uniffi_meta::RecordMetadata> for Record {
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_>>()?,
+ docstring: meta.docstring.clone(),
})
}
}
@@ -107,6 +118,8 @@ pub struct Field {
pub(super) name: String,
pub(super) type_: Type,
pub(super) default: Option<Literal>,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
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<uniffi_meta::FieldMetadata> 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<String>,
// Named type definitions (including aliases).
type_definitions: HashMap<String, Type>,
@@ -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_)