summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src')
-rw-r--r--third_party/rust/uniffi_bindgen/src/backend/filters.rs4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs115
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs251
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs10
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt107
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt117
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt65
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt140
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt30
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt83
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt152
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt14
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt61
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt26
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt161
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt343
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md13
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt39
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt10
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt52
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt140
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs177
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py86
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py98
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py59
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py112
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py9
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py71
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py63
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py45
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py124
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py68
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py9
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py11
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py95
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py17
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/test.rs14
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs79
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb7
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb42
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb10
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs15
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs23
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs209
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift100
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h48
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift113
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift57
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift145
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift69
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift44
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift201
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift17
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift49
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift3
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift125
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs24
-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
-rw-r--r--third_party/rust/uniffi_bindgen/src/lib.rs138
-rw-r--r--third_party/rust/uniffi_bindgen/src/library_mode.rs40
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs21
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs36
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs93
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs13
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs10
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs34
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs8
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs5
153 files changed, 4441 insertions, 2365 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/backend/filters.rs b/third_party/rust/uniffi_bindgen/src/backend/filters.rs
index 0d2da8cab2..f4dde0e420 100644
--- a/third_party/rust/uniffi_bindgen/src/backend/filters.rs
+++ b/third_party/rust/uniffi_bindgen/src/backend/filters.rs
@@ -13,12 +13,12 @@ use std::fmt;
// Need to define an error that implements std::error::Error, which neither String nor
// anyhow::Error do.
#[derive(Debug)]
-struct UniFFIError {
+pub struct UniFFIError {
message: String,
}
impl UniFFIError {
- fn new(message: String) -> Self {
+ pub fn new(message: String) -> Self {
Self { message }
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
index e20020e87c..ae4bffc973 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
@@ -26,6 +26,6 @@ impl CodeType for CallbackInterfaceCodeType {
}
fn initialization_fn(&self) -> Option<String> {
- Some(format!("{}.register", self.ffi_converter_name()))
+ Some(format!("uniffiCallbackInterface{}.register", self.id))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
index 4329f32f4c..8d075bbedb 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
@@ -5,55 +5,81 @@
use super::{AsCodeType, CodeType};
use crate::backend::{Literal, Type};
use crate::ComponentInterface;
-use paste::paste;
-fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String {
- match literal {
- Literal::Null => "null".into(),
- Literal::EmptySequence => "listOf()".into(),
- Literal::EmptyMap => "mapOf()".into(),
+#[derive(Debug)]
+pub struct OptionalCodeType {
+ inner: Type,
+}
- // For optionals
- _ => super::KotlinCodeOracle.find(inner).literal(literal, ci),
+impl OptionalCodeType {
+ pub fn new(inner: Type) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &Type {
+ &self.inner
}
}
-macro_rules! impl_code_type_for_compound {
- ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => {
- paste! {
- #[derive(Debug)]
- pub struct $T {
- inner: Type,
- }
-
- impl $T {
- pub fn new(inner: Type) -> Self {
- Self { inner }
- }
- fn inner(&self) -> &Type {
- &self.inner
- }
- }
-
- impl CodeType for $T {
- fn type_label(&self, ci: &ComponentInterface) -> String {
- format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label(ci))
- }
-
- fn canonical_name(&self) -> String {
- format!($canonical_name_pattern, super::KotlinCodeOracle.find(self.inner()).canonical_name())
- }
-
- fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
- render_literal(literal, self.inner(), ci)
- }
- }
+impl CodeType for OptionalCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ format!(
+ "{}?",
+ super::KotlinCodeOracle.find(self.inner()).type_label(ci)
+ )
+ }
+
+ fn canonical_name(&self) -> String {
+ format!(
+ "Optional{}",
+ super::KotlinCodeOracle.find(self.inner()).canonical_name()
+ )
+ }
+
+ fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::None => "null".into(),
+ Literal::Some { inner } => super::KotlinCodeOracle.find(&self.inner).literal(inner, ci),
+ _ => panic!("Invalid literal for Optional type: {literal:?}"),
}
}
- }
+}
-impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}");
-impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}");
+#[derive(Debug)]
+pub struct SequenceCodeType {
+ inner: Type,
+}
+
+impl SequenceCodeType {
+ pub fn new(inner: Type) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &Type {
+ &self.inner
+ }
+}
+
+impl CodeType for SequenceCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ format!(
+ "List<{}>",
+ super::KotlinCodeOracle.find(self.inner()).type_label(ci)
+ )
+ }
+
+ fn canonical_name(&self) -> String {
+ format!(
+ "Sequence{}",
+ super::KotlinCodeOracle.find(self.inner()).canonical_name()
+ )
+ }
+
+ fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::EmptySequence => "listOf()".into(),
+ _ => panic!("Invalid literal for List type: {literal:?}"),
+ }
+ }
+}
#[derive(Debug)]
pub struct MapCodeType {
@@ -92,7 +118,10 @@ impl CodeType for MapCodeType {
)
}
- fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
- render_literal(literal, &self.value, ci)
+ fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::EmptyMap => "mapOf()".into(),
+ _ => panic!("Invalid literal for Map type: {literal:?}"),
+ }
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs
deleted file mode 100644
index 154e12a381..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use super::CodeType;
-use crate::ComponentInterface;
-
-#[derive(Debug)]
-pub struct ForeignExecutorCodeType;
-
-impl CodeType for ForeignExecutorCodeType {
- fn type_label(&self, _ci: &ComponentInterface) -> String {
- // Kotlin uses a CoroutineScope for ForeignExecutor
- "CoroutineScope".into()
- }
-
- fn canonical_name(&self) -> String {
- "ForeignExecutor".into()
- }
-
- fn initialization_fn(&self) -> Option<String> {
- Some("FfiConverterForeignExecutor.register".into())
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
index 3ecf09d47f..d55c78f760 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
@@ -17,8 +17,8 @@ impl ExternalCodeType {
}
impl CodeType for ExternalCodeType {
- fn type_label(&self, _ci: &ComponentInterface) -> String {
- self.name.clone()
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ super::KotlinCodeOracle.class_name(ci, &self.name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
index 1ed0575a9a..c4fc8e0ed6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
@@ -7,20 +7,21 @@ use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::Debug;
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use askama::Template;
+use camino::Utf8Path;
use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
use serde::{Deserialize, Serialize};
use crate::backend::TemplateExpression;
+use crate::bindings::kotlin;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
mod callback_interface;
mod compounds;
mod custom;
mod enum_;
-mod executor;
mod external;
mod miscellany;
mod object;
@@ -28,6 +29,28 @@ mod primitives;
mod record;
mod variant;
+pub struct KotlinBindingGenerator;
+impl BindingGenerator for KotlinBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ kotlin::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> {
+ if cdylib_name.is_none() {
+ bail!("Generate bindings for Kotlin requires a cdylib, but {library_path} was given");
+ }
+ Ok(())
+ }
+}
+
trait CodeType: Debug {
/// The language specific label used to reference this type. This will be used in
/// method signatures and property declarations.
@@ -73,10 +96,21 @@ trait CodeType: Debug {
pub struct Config {
package_name: Option<String>,
cdylib_name: Option<String>,
+ generate_immutable_records: Option<bool>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
#[serde(default)]
external_packages: HashMap<String, String>,
+ #[serde(default)]
+ android: bool,
+ #[serde(default)]
+ android_cleaner: Option<bool>,
+}
+
+impl Config {
+ pub(crate) fn android_cleaner(&self) -> bool {
+ self.android_cleaner.unwrap_or(self.android)
+ }
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
@@ -103,6 +137,11 @@ impl Config {
"uniffi".into()
}
}
+
+ /// Whether to generate immutable records (`val` instead of `var`)
+ pub fn generate_immutable_records(&self) -> bool {
+ self.generate_immutable_records.unwrap_or(false)
+ }
}
impl BindingsConfig for Config {
@@ -236,7 +275,6 @@ pub struct KotlinWrapper<'a> {
ci: &'a ComponentInterface,
type_helper_code: String,
type_imports: BTreeSet<ImportRequirement>,
- has_async_fns: bool,
}
impl<'a> KotlinWrapper<'a> {
@@ -249,7 +287,6 @@ impl<'a> KotlinWrapper<'a> {
ci,
type_helper_code,
type_imports,
- has_async_fns: ci.has_async_fns(),
}
}
@@ -258,10 +295,6 @@ impl<'a> KotlinWrapper<'a> {
.iter_types()
.map(|t| KotlinCodeOracle.find(t))
.filter_map(|ct| ct.initialization_fn())
- .chain(
- self.has_async_fns
- .then(|| "uniffiRustFutureContinuationCallback.register".into()),
- )
.collect()
}
@@ -301,7 +334,12 @@ impl KotlinCodeOracle {
/// Get the idiomatic Kotlin rendering of a variable name.
fn var_name(&self, nm: &str) -> String {
- format!("`{}`", nm.to_string().to_lower_camel_case())
+ format!("`{}`", self.var_name_raw(nm))
+ }
+
+ /// `var_name` without the backticks. Useful for using in `@Structure.FieldOrder`.
+ pub fn var_name_raw(&self, nm: &str) -> String {
+ nm.to_string().to_lower_camel_case()
}
/// Get the idiomatic Kotlin rendering of an individual enum variant.
@@ -309,14 +347,78 @@ impl KotlinCodeOracle {
nm.to_string().to_shouty_snake_case()
}
- fn ffi_type_label_by_value(ffi_type: &FfiType) -> String {
+ /// Get the idiomatic Kotlin rendering of an FFI callback function name
+ fn ffi_callback_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Kotlin rendering of an FFI struct name
+ fn ffi_struct_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ fn ffi_type_label_by_value(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type)),
+ FfiType::Struct(name) => format!("{}.UniffiByValue", self.ffi_struct_name(name)),
+ _ => self.ffi_type_label(ffi_type),
+ }
+ }
+
+ /// FFI type name to use inside structs
+ ///
+ /// The main requirement here is that all types must have default values or else the struct
+ /// won't work in some JNA contexts.
+ fn ffi_type_label_for_ffi_struct(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ // Make callbacks function pointers nullable. This matches the semantics of a C
+ // function pointer better and allows for `null` as a default value.
+ FfiType::Callback(name) => format!("{}?", self.ffi_callback_name(name)),
+ _ => self.ffi_type_label_by_value(ffi_type),
+ }
+ }
+
+ /// Default values for FFI
+ ///
+ /// This is used to:
+ /// - Set a default return value for error results
+ /// - Set a default for structs, which JNA sometimes requires
+ fn ffi_default_value(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::UInt8 | FfiType::Int8 => "0.toByte()".to_owned(),
+ FfiType::UInt16 | FfiType::Int16 => "0.toShort()".to_owned(),
+ FfiType::UInt32 | FfiType::Int32 => "0".to_owned(),
+ FfiType::UInt64 | FfiType::Int64 => "0.toLong()".to_owned(),
+ FfiType::Float32 => "0.0f".to_owned(),
+ FfiType::Float64 => "0.0".to_owned(),
+ FfiType::RustArcPtr(_) => "Pointer.NULL".to_owned(),
+ FfiType::RustBuffer(_) => "RustBuffer.ByValue()".to_owned(),
+ FfiType::Callback(_) => "null".to_owned(),
+ FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue()".to_owned(),
+ _ => unimplemented!("ffi_default_value: {ffi_type:?}"),
+ }
+ }
+
+ fn ffi_type_label_by_reference(&self, ffi_type: &FfiType) -> String {
match ffi_type {
- FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)),
- _ => Self::ffi_type_label(ffi_type),
+ FfiType::Int8
+ | FfiType::UInt8
+ | FfiType::Int16
+ | FfiType::UInt16
+ | FfiType::Int32
+ | FfiType::UInt32
+ | FfiType::Int64
+ | FfiType::UInt64
+ | FfiType::Float32
+ | FfiType::Float64 => format!("{}ByReference", self.ffi_type_label(ffi_type)),
+ FfiType::RustArcPtr(_) => "PointerByReference".to_owned(),
+ // JNA structs default to ByReference
+ FfiType::RustBuffer(_) | FfiType::Struct(_) => self.ffi_type_label(ffi_type),
+ _ => panic!("{ffi_type:?} by reference is not implemented"),
}
}
- fn ffi_type_label(ffi_type: &FfiType) -> String {
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
// Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not
// support them yet. Thus, we use the signed variants to represent both signed and unsigned
@@ -327,19 +429,35 @@ impl KotlinCodeOracle {
FfiType::Int64 | FfiType::UInt64 => "Long".to_string(),
FfiType::Float32 => "Float".to_string(),
FfiType::Float64 => "Double".to_string(),
+ FfiType::Handle => "Long".to_string(),
FfiType::RustArcPtr(_) => "Pointer".to_string(),
FfiType::RustBuffer(maybe_suffix) => {
format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
}
+ FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue".to_string(),
FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
- FfiType::ForeignCallback => "ForeignCallback".to_string(),
- FfiType::ForeignExecutorHandle => "USize".to_string(),
- FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(),
- FfiType::RustFutureHandle => "Pointer".to_string(),
- FfiType::RustFutureContinuationCallback => {
- "UniFffiRustFutureContinuationCallbackType".to_string()
- }
- FfiType::RustFutureContinuationData => "USize".to_string(),
+ FfiType::Callback(name) => self.ffi_callback_name(name),
+ FfiType::Struct(name) => self.ffi_struct_name(name),
+ FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner),
+ FfiType::VoidPointer => "Pointer".to_string(),
+ }
+ }
+
+ /// Get the name of the interface and class name for an object.
+ ///
+ /// If we support callback interfaces, the interface name is the object name, and the class name is derived from that.
+ /// Otherwise, the class name is the object name and the interface name is derived from that.
+ ///
+ /// This split determines what types `FfiConverter.lower()` inputs. If we support callback
+ /// interfaces, `lower` must lower anything that implements the interface. If not, then lower
+ /// only lowers the concrete class.
+ fn object_names(&self, ci: &ComponentInterface, obj: &Object) -> (String, String) {
+ let class_name = self.class_name(ci, obj.name());
+ if obj.has_callback_interface() {
+ let impl_name = format!("{class_name}Impl");
+ (class_name, impl_name)
+ } else {
+ (format!("{class_name}Interface"), class_name)
}
}
}
@@ -376,12 +494,11 @@ impl<T: AsType> AsCodeType for T {
Type::Duration => Box::new(miscellany::DurationCodeType),
Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
- Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
+ Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)),
Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
Type::CallbackInterface { name, .. } => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
}
- Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional { inner_type } => {
Box::new(compounds::OptionalCodeType::new(*inner_type))
}
@@ -401,6 +518,7 @@ impl<T: AsType> AsCodeType for T {
mod filters {
use super::*;
pub use crate::backend::filters::*;
+ use uniffi_meta::LiteralMetadata;
pub(super) fn type_name(
as_ct: &impl AsCodeType,
@@ -454,8 +572,52 @@ mod filters {
Ok(as_ct.as_codetype().literal(literal, ci))
}
+ // Get the idiomatic Kotlin rendering of an integer.
+ fn int_literal(t: &Option<Type>, base10: String) -> Result<String, askama::Error> {
+ if let Some(t) = t {
+ match t {
+ Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => Ok(base10),
+ Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => Ok(base10 + "u"),
+ _ => Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Only ints are supported.".to_string(),
+ )))),
+ }
+ } else {
+ Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Enum hasn't defined a repr".to_string(),
+ ))))
+ }
+ }
+
+ // Get the idiomatic Kotlin rendering of an individual enum variant's discriminant
+ pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> {
+ let literal = e.variant_discr(*index).expect("invalid index");
+ match literal {
+ // Kotlin doesn't convert between signed and unsigned by default
+ // so we'll need to make sure we define the type as appropriately
+ LiteralMetadata::UInt(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()),
+ LiteralMetadata::Int(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()),
+ _ => Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Only ints are supported.".to_string(),
+ )))),
+ }
+ }
+
pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> {
- Ok(KotlinCodeOracle::ffi_type_label_by_value(type_))
+ Ok(KotlinCodeOracle.ffi_type_label_by_value(type_))
+ }
+
+ pub fn ffi_type_name_for_ffi_struct(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_type_label_for_ffi_struct(type_))
+ }
+
+ pub fn ffi_default_value(type_: FfiType) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_default_value(&type_))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a function name.
+ pub fn class_name(nm: &str, ci: &ComponentInterface) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.class_name(ci, nm))
}
/// Get the idiomatic Kotlin rendering of a function name.
@@ -468,6 +630,11 @@ mod filters {
Ok(KotlinCodeOracle.var_name(nm))
}
+ /// Get the idiomatic Kotlin rendering of a variable name.
+ pub fn var_name_raw(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.var_name_raw(nm))
+ }
+
/// Get a String representing the name used for an individual enum variant.
pub fn variant_name(v: &Variant) -> Result<String, askama::Error> {
Ok(KotlinCodeOracle.enum_variant_name(v.name()))
@@ -478,13 +645,30 @@ mod filters {
Ok(KotlinCodeOracle.convert_error_suffix(&name))
}
+ /// Get the idiomatic Kotlin rendering of an FFI callback function name
+ pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_callback_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of an FFI struct name
+ pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_struct_name(nm))
+ }
+
+ pub fn object_names(
+ obj: &Object,
+ ci: &ComponentInterface,
+ ) -> Result<(String, String), askama::Error> {
+ Ok(KotlinCodeOracle.object_names(ci, obj))
+ }
+
pub fn async_poll(
callable: impl Callable,
ci: &ComponentInterface,
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_poll(ci);
Ok(format!(
- "{{ future, continuation -> _UniFFILib.INSTANCE.{ffi_func}(future, continuation) }}"
+ "{{ future, callback, continuation -> UniffiLib.INSTANCE.{ffi_func}(future, callback, continuation) }}"
))
}
@@ -493,7 +677,7 @@ mod filters {
ci: &ComponentInterface,
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_complete(ci);
- let call = format!("_UniFFILib.INSTANCE.{ffi_func}(future, continuation)");
+ let call = format!("UniffiLib.INSTANCE.{ffi_func}(future, continuation)");
let call = match callable.return_type() {
Some(Type::External {
kind: ExternalKind::DataClass,
@@ -502,7 +686,7 @@ mod filters {
}) => {
// Need to convert the RustBuffer from our package to the RustBuffer of the external package
let suffix = KotlinCodeOracle.class_name(ci, &name);
- format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}")
+ format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity.toULong(), it.len.toULong(), it.data) }}")
}
_ => call,
};
@@ -515,7 +699,7 @@ mod filters {
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_free(ci);
Ok(format!(
- "{{ future -> _UniFFILib.INSTANCE.{ffi_func}(future) }}"
+ "{{ future -> UniffiLib.INSTANCE.{ffi_func}(future) }}"
))
}
@@ -527,4 +711,13 @@ mod filters {
pub fn unquote(nm: &str) -> Result<String, askama::Error> {
Ok(nm.trim_matches('`').to_string())
}
+
+ /// Get the idiomatic Kotlin rendering of docstring
+ pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
+ let middle = textwrap::indent(&textwrap::dedent(docstring), " * ");
+ let wrapped = format!("/**\n{middle}\n */");
+
+ let spaces = usize::try_from(*spaces).unwrap_or_default();
+ Ok(textwrap::indent(&wrapped, &" ".repeat(spaces)))
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
index c39ae59cce..5a4305d14a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
@@ -3,25 +3,32 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::CodeType;
-use crate::ComponentInterface;
+use crate::{interface::ObjectImpl, ComponentInterface};
#[derive(Debug)]
pub struct ObjectCodeType {
- id: String,
+ name: String,
+ imp: ObjectImpl,
}
impl ObjectCodeType {
- pub fn new(id: String) -> Self {
- Self { id }
+ pub fn new(name: String, imp: ObjectImpl) -> Self {
+ Self { name, imp }
}
}
impl CodeType for ObjectCodeType {
fn type_label(&self, ci: &ComponentInterface) -> String {
- super::KotlinCodeOracle.class_name(ci, &self.id)
+ super::KotlinCodeOracle.class_name(ci, &self.name)
}
fn canonical_name(&self) -> String {
- format!("Type{}", self.id)
+ format!("Type{}", self.name)
+ }
+
+ fn initialization_fn(&self) -> Option<String> {
+ self.imp
+ .has_callback_interface()
+ .then(|| format!("uniffiCallbackInterface{}.register", self.name))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
index 22495fa209..0bc5a5d99e 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
@@ -9,7 +9,11 @@ use paste::paste;
fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String {
fn typed_number(type_: &Type, num_str: String) -> String {
- match type_ {
+ let unwrapped_type = match type_ {
+ Type::Optional { inner_type } => inner_type,
+ t => t,
+ };
+ match unwrapped_type {
// Bytes, Shorts and Ints can all be inferred from the type.
Type::Int8 | Type::Int16 | Type::Int32 => num_str,
Type::Int64 => format!("{num_str}L"),
@@ -19,7 +23,7 @@ fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String {
Type::Float32 => format!("{num_str}f"),
Type::Float64 => num_str,
- _ => panic!("Unexpected literal: {num_str} is not a number"),
+ _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"),
}
}
@@ -56,7 +60,7 @@ macro_rules! impl_code_type_for_primitive {
impl CodeType for $T {
fn type_label(&self, _ci: &ComponentInterface) -> String {
- $class_name.into()
+ format!("kotlin.{}", $class_name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
index c6a32655f2..b28fbd2c80 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
@@ -1,44 +1,117 @@
// Async return type handlers
-internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort()
-internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort()
+internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte()
+internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte()
-internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>()
+internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>()
// FFI type for Rust future continuations
-internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType {
- override fun callback(continuationHandle: USize, pollResult: Short) {
- uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult)
- }
-
- internal fun register(lib: _UniFFILib) {
- lib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(this)
+internal object uniffiRustFutureContinuationCallbackImpl: UniffiRustFutureContinuationCallback {
+ override fun callback(data: Long, pollResult: Byte) {
+ uniffiContinuationHandleMap.remove(data).resume(pollResult)
}
}
internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
- rustFuture: Pointer,
- pollFunc: (Pointer, USize) -> Unit,
- completeFunc: (Pointer, RustCallStatus) -> F,
- freeFunc: (Pointer) -> Unit,
+ rustFuture: Long,
+ pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit,
+ completeFunc: (Long, UniffiRustCallStatus) -> F,
+ freeFunc: (Long) -> Unit,
liftFunc: (F) -> T,
- errorHandler: CallStatusErrorHandler<E>
+ errorHandler: UniffiRustCallStatusErrorHandler<E>
): T {
try {
do {
- val pollResult = suspendCancellableCoroutine<Short> { continuation ->
+ val pollResult = suspendCancellableCoroutine<Byte> { continuation ->
pollFunc(
rustFuture,
+ uniffiRustFutureContinuationCallbackImpl,
uniffiContinuationHandleMap.insert(continuation)
)
}
} while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY);
return liftFunc(
- rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
+ uniffiRustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
)
} finally {
freeFunc(rustFuture)
}
}
+{%- if ci.has_async_callback_interface_definition() %}
+internal inline fun<T> uniffiTraitInterfaceCallAsync(
+ crossinline makeCall: suspend () -> T,
+ crossinline handleSuccess: (T) -> Unit,
+ crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit,
+): UniffiForeignFuture {
+ // Using `GlobalScope` is labeled as a "delicate API" and generally discouraged in Kotlin programs, since it breaks structured concurrency.
+ // However, our parent task is a Rust future, so we're going to need to break structure concurrency in any case.
+ //
+ // Uniffi does its best to support structured concurrency across the FFI.
+ // If the Rust future is dropped, `uniffiForeignFutureFreeImpl` is called, which will cancel the Kotlin coroutine if it's still running.
+ @OptIn(DelicateCoroutinesApi::class)
+ val job = GlobalScope.launch {
+ try {
+ handleSuccess(makeCall())
+ } catch(e: Exception) {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(e.toString()),
+ )
+ )
+ }
+ }
+ val handle = uniffiForeignFutureHandleMap.insert(job)
+ return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl)
+}
+
+internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallAsyncWithError(
+ crossinline makeCall: suspend () -> T,
+ crossinline handleSuccess: (T) -> Unit,
+ crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit,
+ crossinline lowerError: (E) -> RustBuffer.ByValue,
+): UniffiForeignFuture {
+ // See uniffiTraitInterfaceCallAsync for details on `DelicateCoroutinesApi`
+ @OptIn(DelicateCoroutinesApi::class)
+ val job = GlobalScope.launch {
+ try {
+ handleSuccess(makeCall())
+ } catch(e: Exception) {
+ if (e is E) {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_ERROR,
+ lowerError(e),
+ )
+ )
+ } else {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(e.toString()),
+ )
+ )
+ }
+ }
+ }
+ val handle = uniffiForeignFutureHandleMap.insert(job)
+ return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl)
+}
+
+internal val uniffiForeignFutureHandleMap = UniffiHandleMap<Job>()
+
+internal object uniffiForeignFutureFreeImpl: UniffiForeignFutureFree {
+ override fun callback(handle: Long) {
+ val job = uniffiForeignFutureHandleMap.remove(handle)
+ if (!job.isCompleted) {
+ job.cancel()
+ }
+ }
+}
+
+// For testing
+public fun uniffiForeignFutureHandleCount() = uniffiForeignFutureHandleMap.size
+
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
index 8cfa2ce000..c6b266066d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
@@ -11,7 +11,7 @@ public object FfiConverterBoolean: FfiConverter<Boolean, Byte> {
return if (value) 1.toByte() else 0.toByte()
}
- override fun allocationSize(value: Boolean) = 1
+ override fun allocationSize(value: Boolean) = 1UL
override fun write(value: Boolean, buf: ByteBuffer) {
buf.put(lower(value))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
index 4840a199b4..c9449069e2 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
@@ -5,8 +5,8 @@ public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> {
buf.get(byteArr)
return byteArr
}
- override fun allocationSize(value: ByteArray): Int {
- return 4 + value.size
+ override fun allocationSize(value: ByteArray): ULong {
+ return 4UL + value.size.toULong()
}
override fun write(value: ByteArray, buf: ByteBuffer) {
buf.putInt(value.size)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt
new file mode 100644
index 0000000000..30a39d9afb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt
@@ -0,0 +1,117 @@
+{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
+
+{%- let trait_impl=format!("uniffiCallbackInterface{}", name) %}
+
+// Put the implementation in an object so we don't pollute the top-level namespace
+internal object {{ trait_impl }} {
+ {%- for (ffi_callback, meth) in vtable_methods.iter() %}
+ internal object {{ meth.name()|var_name }}: {{ ffi_callback.name()|ffi_callback_name }} {
+ override fun callback(
+ {%- for arg in ffi_callback.arguments() -%}
+ {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }},
+ {%- endfor -%}
+ {%- if ffi_callback.has_rust_call_status_arg() -%}
+ uniffiCallStatus: UniffiRustCallStatus,
+ {%- endif -%}
+ )
+ {%- match ffi_callback.return_type() %}
+ {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }},
+ {%- when None %}
+ {%- endmatch %} {
+ val uniffiObj = {{ ffi_converter_name }}.handleMap.get(uniffiHandle)
+ val makeCall = {% if meth.is_async() %}suspend {% endif %}{ ->
+ uniffiObj.{{ meth.name()|fn_name() }}(
+ {%- for arg in meth.arguments() %}
+ {{ arg|lift_fn }}({{ arg.name()|var_name }}),
+ {%- endfor %}
+ )
+ }
+ {%- if !meth.is_async() %}
+
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ val writeReturn = { value: {{ return_type|type_name(ci) }} -> uniffiOutReturn.setValue({{ return_type|lower_fn }}(value)) }
+ {%- when None %}
+ val writeReturn = { _: Unit -> Unit }
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn)
+ {%- when Some(error_type) %}
+ uniffiTraitInterfaceCallWithError(
+ uniffiCallStatus,
+ makeCall,
+ writeReturn,
+ { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) }
+ )
+ {%- endmatch %}
+
+ {%- else %}
+ val uniffiHandleSuccess = { {% if meth.return_type().is_some() %}returnValue{% else %}_{% endif %}: {% match meth.return_type() %}{%- when Some(return_type) %}{{ return_type|type_name(ci) }}{%- when None %}Unit{% endmatch %} ->
+ val uniffiResult = {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ return_type|lower_fn }}(returnValue),
+ {%- when None %}
+ {%- endmatch %}
+ UniffiRustCallStatus.ByValue()
+ )
+ uniffiResult.write()
+ uniffiFutureCallback.callback(uniffiCallbackData, uniffiResult)
+ }
+ val uniffiHandleError = { callStatus: UniffiRustCallStatus.ByValue ->
+ uniffiFutureCallback.callback(
+ uniffiCallbackData,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ return_type.into()|ffi_default_value }},
+ {%- when None %}
+ {%- endmatch %}
+ callStatus,
+ ),
+ )
+ }
+
+ uniffiOutReturn.uniffiSetValue(
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffiTraitInterfaceCallAsync(
+ makeCall,
+ uniffiHandleSuccess,
+ uniffiHandleError
+ )
+ {%- when Some(error_type) %}
+ uniffiTraitInterfaceCallAsyncWithError(
+ makeCall,
+ uniffiHandleSuccess,
+ uniffiHandleError,
+ { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) }
+ )
+ {%- endmatch %}
+ )
+ {%- endif %}
+ }
+ }
+ {%- endfor %}
+
+ internal object uniffiFree: {{ "CallbackInterfaceFree"|ffi_callback_name }} {
+ override fun callback(handle: Long) {
+ {{ ffi_converter_name }}.handleMap.remove(handle)
+ }
+ }
+
+ internal var vtable = {{ vtable|ffi_type_name_by_value }}(
+ {%- for (ffi_callback, meth) in vtable_methods.iter() %}
+ {{ meth.name()|var_name() }},
+ {%- endfor %}
+ uniffiFree,
+ )
+
+ // Registers the foreign callback with the Rust side.
+ // This method is generated for each callback interface.
+ internal fun register(lib: UniffiLib) {
+ lib.{{ ffi_init_callback.name() }}(vtable)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
index 62a71e02f1..d58a651e24 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
@@ -1,43 +1,3 @@
-internal typealias Handle = Long
-internal class ConcurrentHandleMap<T>(
- private val leftMap: MutableMap<Handle, T> = mutableMapOf(),
- private val rightMap: MutableMap<T, Handle> = mutableMapOf()
-) {
- private val lock = java.util.concurrent.locks.ReentrantLock()
- private val currentHandle = AtomicLong(0L)
- private val stride = 1L
-
- fun insert(obj: T): Handle =
- lock.withLock {
- rightMap[obj] ?:
- currentHandle.getAndAdd(stride)
- .also { handle ->
- leftMap[handle] = obj
- rightMap[obj] = handle
- }
- }
-
- fun get(handle: Handle) = lock.withLock {
- leftMap[handle]
- }
-
- fun delete(handle: Handle) {
- this.remove(handle)
- }
-
- fun remove(handle: Handle): T? =
- lock.withLock {
- leftMap.remove(handle)?.let { obj ->
- rightMap.remove(obj)
- obj
- }
- }
-}
-
-interface ForeignCallback : com.sun.jna.Callback {
- public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int
-}
-
// Magic number for the Rust proxy to call using the same mechanism as every other method,
// to free the callback once it's dropped by Rust.
internal const val IDX_CALLBACK_FREE = 0
@@ -46,31 +6,22 @@ internal const val UNIFFI_CALLBACK_SUCCESS = 0
internal const val UNIFFI_CALLBACK_ERROR = 1
internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
-public abstract class FfiConverterCallbackInterface<CallbackInterface>(
- protected val foreignCallback: ForeignCallback
-): FfiConverter<CallbackInterface, Handle> {
- private val handleMap = ConcurrentHandleMap<CallbackInterface>()
-
- // Registers the foreign callback with the Rust side.
- // This method is generated for each callback interface.
- internal abstract fun register(lib: _UniFFILib)
+public abstract class FfiConverterCallbackInterface<CallbackInterface: Any>: FfiConverter<CallbackInterface, Long> {
+ internal val handleMap = UniffiHandleMap<CallbackInterface>()
- fun drop(handle: Handle): RustBuffer.ByValue {
- return handleMap.remove(handle).let { RustBuffer.ByValue() }
+ internal fun drop(handle: Long) {
+ handleMap.remove(handle)
}
- override fun lift(value: Handle): CallbackInterface {
- return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug")
+ override fun lift(value: Long): CallbackInterface {
+ return handleMap.get(value)
}
override fun read(buf: ByteBuffer) = lift(buf.getLong())
- override fun lower(value: CallbackInterface) =
- handleMap.insert(value).also {
- assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." }
- }
+ override fun lower(value: CallbackInterface) = handleMap.insert(value)
- override fun allocationSize(value: CallbackInterface) = 8
+ override fun allocationSize(value: CallbackInterface) = 8UL
override fun write(value: CallbackInterface, buf: ByteBuffer) {
buf.putLong(lower(value))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
index 5a29f0acc3..d2cdee4f33 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
@@ -1,129 +1,13 @@
{%- let cbi = ci|get_callback_interface_definition(name) %}
-{%- let type_name = cbi|type_name(ci) %}
-{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %}
-
-{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
-{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
-{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }}
-{{- self.add_import("kotlin.concurrent.withLock") }}
-
-// Declaration and FfiConverters for {{ type_name }} Callback Interface
-
-public interface {{ type_name }} {
- {% for meth in cbi.methods() -%}
- fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
- {%- else -%}
- {%- endmatch %}
- {% endfor %}
- companion object
-}
-
-// The ForeignCallback that is passed to Rust.
-internal class {{ foreign_callback }} : ForeignCallback {
- @Suppress("TooGenericExceptionCaught")
- override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
- val cb = {{ ffi_converter_name }}.lift(handle)
- return when (method) {
- IDX_CALLBACK_FREE -> {
- {{ ffi_converter_name }}.drop(handle)
- // Successful return
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- UNIFFI_CALLBACK_SUCCESS
- }
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
- {{ loop.index }} -> {
- // Call the method, write to outBuf and return a status code
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info
- try {
- this.{{ method_name }}(cb, argsData, argsLen, outBuf)
- } catch (e: Throwable) {
- // Unexpected error
- try {
- // Try to serialize the error into a string
- outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString()))
- } catch (e: Throwable) {
- // If that fails, then it's time to give up and just return
- }
- UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- }
- {% endfor %}
- else -> {
- // An unexpected error happened.
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- try {
- // Try to serialize the error into a string
- outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index"))
- } catch (e: Throwable) {
- // If that fails, then it's time to give up and just return
- }
- UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- }
- }
-
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
- @Suppress("UNUSED_PARAMETER")
- private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
- {%- if meth.arguments().len() > 0 %}
- val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also {
- it.order(ByteOrder.BIG_ENDIAN)
- }
- {%- endif %}
-
- {%- match meth.return_type() %}
- {%- when Some with (return_type) %}
- fun makeCall() : Int {
- val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}(
- {%- for arg in meth.arguments() %}
- {{ arg|read_fn }}(argsBuf)
- {% if !loop.last %}, {% endif %}
- {%- endfor %}
- )
- outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue))
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- when None %}
- fun makeCall() : Int {
- kotlinCallbackInterface.{{ meth.name()|fn_name }}(
- {%- for arg in meth.arguments() %}
- {{ arg|read_fn }}(argsBuf)
- {%- if !loop.last %}, {% endif %}
- {%- endfor %}
- )
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- endmatch %}
-
- {%- match meth.throws_type() %}
- {%- when None %}
- fun makeCallAndHandleError() : Int = makeCall()
- {%- when Some(error_type) %}
- fun makeCallAndHandleError() : Int = try {
- makeCall()
- } catch (e: {{ error_type|type_name(ci) }}) {
- // Expected error, serialize it into outBuf
- outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e))
- UNIFFI_CALLBACK_ERROR
- }
- {%- endmatch %}
-
- return makeCallAndHandleError()
- }
- {% endfor %}
-}
-
-// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust.
-public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>(
- foreignCallback = {{ foreign_callback }}()
-) {
- override fun register(lib: _UniFFILib) {
- rustCall() { status ->
- lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status)
- }
- }
-}
+{%- let ffi_init_callback = cbi.ffi_init_callback() %}
+{%- let interface_name = cbi|type_name(ci) %}
+{%- let interface_docstring = cbi.docstring() %}
+{%- let methods = cbi.methods() %}
+{%- let vtable = cbi.vtable() %}
+{%- let vtable_methods = cbi.vtable_methods() %}
+
+{% include "Interface.kt" %}
+{% include "CallbackInterfaceImpl.kt" %}
+
+// The ffiConverter which transforms the Callbacks in to handles to pass to Rust.
+public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ interface_name }}>()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
index 04150c5d78..aeb5f58002 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
@@ -49,7 +49,7 @@ public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_nam
return {{ config.into_custom.render("builtinValue") }}
}
- override fun allocationSize(value: {{ name }}): Int {
+ override fun allocationSize(value: {{ name }}): ULong {
val builtinValue = {{ config.from_custom.render("value") }}
return {{ builtin|allocation_size_fn }}(builtinValue)
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
index 4237c6f9a8..62e02607f3 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
@@ -14,7 +14,7 @@ public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> {
}
// 8 bytes for seconds, 4 bytes for nanoseconds
- override fun allocationSize(value: java.time.Duration) = 12
+ override fun allocationSize(value: java.time.Duration) = 12UL
override fun write(value: java.time.Duration, buf: ByteBuffer) {
if (value.seconds < 0) {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
index d4c4a1684a..8d1c2235ec 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
@@ -7,12 +7,25 @@
{%- if e.is_flat() %}
+{%- call kt::docstring(e, 0) %}
+{% match e.variant_discr_type() %}
+{% when None %}
enum class {{ type_name }} {
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %}
{%- endfor %}
companion object
}
+{% when Some with (variant_discr_type) %}
+enum class {{ type_name }}(val value: {{ variant_discr_type|type_name(ci) }}) {
+ {% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
+ {{ variant|variant_name }}({{ e|variant_discr_literal(loop.index0) }}){% if loop.last %};{% else %},{% endif %}
+ {%- endfor %}
+ companion object
+}
+{% endmatch %}
public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
override fun read(buf: ByteBuffer) = try {
@@ -21,7 +34,7 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}
throw RuntimeException("invalid enum value, something is very wrong!!", e)
}
- override fun allocationSize(value: {{ type_name }}) = 4
+ override fun allocationSize(value: {{ type_name }}) = 4UL
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
buf.putInt(value.ordinal + 1)
@@ -30,15 +43,18 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}
{% else %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} {
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{% if !variant.has_fields() -%}
object {{ variant|type_name(ci) }} : {{ type_name }}()
{% else -%}
data class {{ variant|type_name(ci) }}(
- {% for field in variant.fields() -%}
- val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
- {% endfor -%}
+ {%- for field in variant.fields() -%}
+ {%- call kt::docstring(field, 8) %}
+ val {% call kt::field_name(field, loop.index) %}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
+ {%- endfor -%}
) : {{ type_name }}() {
companion object
}
@@ -83,9 +99,9 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
is {{ type_name }}.{{ variant|type_name(ci) }} -> {
// Add the size for the Int that specifies the variant plus the size needed for all fields
(
- 4
+ 4UL
{%- for field in variant.fields() %}
- + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ + {{ field|allocation_size_fn }}(value.{%- call kt::field_name(field, loop.index) -%})
{%- endfor %}
)
}
@@ -98,7 +114,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
is {{ type_name }}.{{ variant|type_name(ci) }} -> {
buf.putInt({{ loop.index }})
{%- for field in variant.fields() %}
- {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {{ field|write_fn }}(value.{%- call kt::field_name(field, loop.index) -%}, buf)
{%- endfor %}
Unit
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
index 986db5424d..4760c03fd6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
@@ -3,24 +3,26 @@
{%- let canonical_type_name = type_|canonical_name %}
{% if e.is_flat() %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} {
- // Each variant is a nested class
- // Flat enums carries a string error message, so no special implementation is necessary.
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message)
{% endfor %}
- companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> {
override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf)
}
}
{%- else %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} {
- // Each variant is a nested class
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{%- let variant_name = variant|error_variant_name %}
class {{ variant_name }}(
{% for field in variant.fields() -%}
+ {%- call kt::docstring(field, 8) %}
val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
{% endfor -%}
) : {{ type_name }}() {
@@ -29,7 +31,7 @@ sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Di
}
{% endfor %}
- companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> {
override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf)
}
@@ -76,15 +78,15 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
{%- endif %}
}
- override fun allocationSize(value: {{ type_name }}): Int {
+ override fun allocationSize(value: {{ type_name }}): ULong {
{%- if e.is_flat() %}
- return 4
+ return 4UL
{%- else %}
return when(value) {
{%- for variant in e.variants() %}
is {{ type_name }}.{{ variant|error_variant_name }} -> (
// Add the size for the Int that specifies the variant plus the size needed for all fields
- 4
+ 4UL
{%- for field in variant.fields() %}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
index 0fade7a0bc..b7e77f0b2d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
@@ -1,5 +1,5 @@
{%- let package_name=self.external_type_package_name(module_path, namespace) %}
-{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %}
+{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name|class_name(ci)) %}
{%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %}
{%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %}
{%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
index 3b2c9d225a..0de90b9c4b 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
@@ -20,7 +20,7 @@ public interface FfiConverter<KotlinType, FfiType> {
// encoding, so we pessimistically allocate the largest size possible (3
// bytes per codepoint). Allocating extra bytes is not really a big deal
// because the `RustBuffer` is short-lived.
- fun allocationSize(value: KotlinType): Int
+ fun allocationSize(value: KotlinType): ULong
// Write a Kotlin type to a `ByteBuffer`
fun write(value: KotlinType, buf: ByteBuffer)
@@ -34,11 +34,11 @@ public interface FfiConverter<KotlinType, FfiType> {
fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
val rbuf = RustBuffer.alloc(allocationSize(value))
try {
- val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also {
+ val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity).also {
it.order(ByteOrder.BIG_ENDIAN)
}
write(value, bbuf)
- rbuf.writeField("len", bbuf.position())
+ rbuf.writeField("len", bbuf.position().toLong())
return rbuf
} catch (e: Throwable) {
RustBuffer.free(rbuf)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
index eafec5d122..be91ac8fcb 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterFloat: FfiConverter<Float, Float> {
return value
}
- override fun allocationSize(value: Float) = 4
+ override fun allocationSize(value: Float) = 4UL
override fun write(value: Float, buf: ByteBuffer) {
buf.putFloat(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
index 9fc2892c95..5eb465f0df 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterDouble: FfiConverter<Double, Double> {
return value
}
- override fun allocationSize(value: Double) = 8
+ override fun allocationSize(value: Double) = 8UL
override fun write(value: Double, buf: ByteBuffer) {
buf.putDouble(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
deleted file mode 100644
index 3544b2f9e6..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-{{ self.add_import("kotlinx.coroutines.CoroutineScope") }}
-{{ self.add_import("kotlinx.coroutines.delay") }}
-{{ self.add_import("kotlinx.coroutines.isActive") }}
-{{ self.add_import("kotlinx.coroutines.launch") }}
-
-internal const val UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0.toByte()
-internal const val UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED = 1.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2.toByte()
-
-// Callback function to execute a Rust task. The Kotlin code schedules these in a coroutine then
-// invokes them.
-internal interface UniFfiRustTaskCallback : com.sun.jna.Callback {
- fun callback(rustTaskData: Pointer?, statusCode: Byte)
-}
-
-internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback {
- fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte {
- if (rustTask == null) {
- FfiConverterForeignExecutor.drop(handle)
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- } else {
- val coroutineScope = FfiConverterForeignExecutor.lift(handle)
- if (coroutineScope.isActive) {
- val job = coroutineScope.launch {
- if (delayMs > 0) {
- delay(delayMs.toLong())
- }
- rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS)
- }
- job.invokeOnCompletion { cause ->
- if (cause != null) {
- rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_CANCELLED)
- }
- }
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- } else {
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED
- }
- }
- }
-}
-
-public object FfiConverterForeignExecutor: FfiConverter<CoroutineScope, USize> {
- internal val handleMap = UniFfiHandleMap<CoroutineScope>()
-
- internal fun drop(handle: USize) {
- handleMap.remove(handle)
- }
-
- internal fun register(lib: _UniFFILib) {
- {%- match ci.ffi_foreign_executor_callback_set() %}
- {%- when Some with (fn) %}
- lib.{{ fn.name() }}(UniFfiForeignExecutorCallback)
- {%- when None %}
- {#- No foreign executor, we don't set anything #}
- {% endmatch %}
- }
-
- // Number of live handles, exposed so we can test the memory management
- public fun handleCount() : Int {
- return handleMap.size
- }
-
- override fun allocationSize(value: CoroutineScope) = USize.size
-
- override fun lift(value: USize): CoroutineScope {
- return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift")
- }
-
- override fun read(buf: ByteBuffer): CoroutineScope {
- return lift(USize.readFromBuffer(buf))
- }
-
- override fun lower(value: CoroutineScope): USize {
- return handleMap.insert(value)
- }
-
- override fun write(value: CoroutineScope, buf: ByteBuffer) {
- lower(value).writeToBuffer(buf)
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt
new file mode 100644
index 0000000000..3a56648190
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt
@@ -0,0 +1,27 @@
+// Map handles to objects
+//
+// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code.
+internal class UniffiHandleMap<T: Any> {
+ private val map = ConcurrentHashMap<Long, T>()
+ private val counter = java.util.concurrent.atomic.AtomicLong(0)
+
+ val size: Int
+ get() = map.size
+
+ // Insert a new object into the handle map and get a handle for it
+ fun insert(obj: T): Long {
+ val handle = counter.getAndAdd(1)
+ map.put(handle, obj)
+ return handle
+ }
+
+ // Get an object from the handle map
+ fun get(handle: Long): T {
+ return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle")
+ }
+
+ // Remove an entry from the handlemap and get the Kotlin object back
+ fun remove(handle: Long): T {
+ return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle")
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
index 382a5f7413..1fdbd3ffc0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
@@ -1,30 +1,43 @@
// A handful of classes and functions to support the generated data structures.
// This would be a good candidate for isolating in its own ffi-support lib.
-// Error runtime.
+
+internal const val UNIFFI_CALL_SUCCESS = 0.toByte()
+internal const val UNIFFI_CALL_ERROR = 1.toByte()
+internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte()
+
@Structure.FieldOrder("code", "error_buf")
-internal open class RustCallStatus : Structure() {
+internal open class UniffiRustCallStatus : Structure() {
@JvmField var code: Byte = 0
@JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
- class ByValue: RustCallStatus(), Structure.ByValue
+ class ByValue: UniffiRustCallStatus(), Structure.ByValue
fun isSuccess(): Boolean {
- return code == 0.toByte()
+ return code == UNIFFI_CALL_SUCCESS
}
fun isError(): Boolean {
- return code == 1.toByte()
+ return code == UNIFFI_CALL_ERROR
}
fun isPanic(): Boolean {
- return code == 2.toByte()
+ return code == UNIFFI_CALL_UNEXPECTED_ERROR
+ }
+
+ companion object {
+ fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue {
+ val callStatus = UniffiRustCallStatus.ByValue()
+ callStatus.code = code
+ callStatus.error_buf = errorBuf
+ return callStatus
+ }
}
}
class InternalException(message: String) : Exception(message)
// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
-interface CallStatusErrorHandler<E> {
+interface UniffiRustCallStatusErrorHandler<E> {
fun lift(error_buf: RustBuffer.ByValue): E;
}
@@ -33,15 +46,15 @@ interface CallStatusErrorHandler<E> {
// synchronize itself
// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
-private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
- var status = RustCallStatus();
+private inline fun <U, E: Exception> uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler<E>, callback: (UniffiRustCallStatus) -> U): U {
+ var status = UniffiRustCallStatus();
val return_value = callback(status)
- checkCallStatus(errorHandler, status)
+ uniffiCheckCallStatus(errorHandler, status)
return return_value
}
-// Check RustCallStatus and throw an error if the call wasn't successful
-private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E>, status: RustCallStatus) {
+// Check UniffiRustCallStatus and throw an error if the call wasn't successful
+private fun<E: Exception> uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler<E>, status: UniffiRustCallStatus) {
if (status.isSuccess()) {
return
} else if (status.isError()) {
@@ -60,8 +73,8 @@ private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E
}
}
-// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
-object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
+// UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
+object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler<InternalException> {
override fun lift(error_buf: RustBuffer.ByValue): InternalException {
RustBuffer.free(error_buf)
return InternalException("Unexpected CALL_ERROR")
@@ -69,93 +82,38 @@ object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
}
// Call a rust function that returns a plain value
-private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
- return rustCallWithError(NullCallStatusErrorHandler, callback);
+private inline fun <U> uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U {
+ return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback);
}
-// IntegerType that matches Rust's `usize` / C's `size_t`
-public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) {
- // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin.
- override fun toByte() = toInt().toByte()
- // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed.
- @Deprecated("`toInt().toChar()` is deprecated")
- override fun toChar() = toInt().toChar()
- override fun toShort() = toInt().toShort()
-
- fun writeToBuffer(buf: ByteBuffer) {
- // Make sure we always write usize integers using native byte-order, since they may be
- // casted to pointer values
- buf.order(ByteOrder.nativeOrder())
- try {
- when (Native.SIZE_T_SIZE) {
- 4 -> buf.putInt(toInt())
- 8 -> buf.putLong(toLong())
- else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
- }
- } finally {
- buf.order(ByteOrder.BIG_ENDIAN)
- }
- }
-
- companion object {
- val size: Int
- get() = Native.SIZE_T_SIZE
-
- fun readFromBuffer(buf: ByteBuffer) : USize {
- // Make sure we always read usize integers using native byte-order, since they may be
- // casted from pointer values
- buf.order(ByteOrder.nativeOrder())
- try {
- return when (Native.SIZE_T_SIZE) {
- 4 -> USize(buf.getInt().toLong())
- 8 -> USize(buf.getLong())
- else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
- }
- } finally {
- buf.order(ByteOrder.BIG_ENDIAN)
- }
- }
+internal inline fun<T> uniffiTraitInterfaceCall(
+ callStatus: UniffiRustCallStatus,
+ makeCall: () -> T,
+ writeReturn: (T) -> Unit,
+) {
+ try {
+ writeReturn(makeCall())
+ } catch(e: Exception) {
+ callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR
+ callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString())
}
}
-
-// Map handles to objects
-//
-// This is used when the Rust code expects an opaque pointer to represent some foreign object.
-// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an
-// object reference , nor does it support leaking a reference to Rust.
-//
-// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to
-// Rust when it needs an opaque pointer.
-//
-// TODO: refactor callbacks to use this class
-internal class UniFfiHandleMap<T: Any> {
- private val map = ConcurrentHashMap<USize, T>()
- // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible
- // values seems like enough. If somehow we generate 4 billion handles, then this will wrap
- // around back to zero and we can assume the first handle generated will have been dropped by
- // then.
- private val counter = java.util.concurrent.atomic.AtomicInteger(0)
-
- val size: Int
- get() = map.size
-
- fun insert(obj: T): USize {
- val handle = USize(counter.getAndAdd(1).toLong())
- map.put(handle, obj)
- return handle
- }
-
- fun get(handle: USize): T? {
- return map.get(handle)
- }
-
- fun remove(handle: USize): T? {
- return map.remove(handle)
+internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallWithError(
+ callStatus: UniffiRustCallStatus,
+ makeCall: () -> T,
+ writeReturn: (T) -> Unit,
+ lowerError: (E) -> RustBuffer.ByValue
+) {
+ try {
+ writeReturn(makeCall())
+ } catch(e: Exception) {
+ if (e is E) {
+ callStatus.code = UNIFFI_CALL_ERROR
+ callStatus.error_buf = lowerError(e)
+ } else {
+ callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR
+ callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString())
+ }
}
}
-
-// FFI type for Rust future continuations
-internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback {
- fun callback(continuationHandle: USize, pollResult: Short);
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
index 75564276be..de8296fff6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterShort: FfiConverter<Short, Short> {
return value
}
- override fun allocationSize(value: Short) = 2
+ override fun allocationSize(value: Short) = 2UL
override fun write(value: Short, buf: ByteBuffer) {
buf.putShort(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
index b7a8131c8b..171809a9c4 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterInt: FfiConverter<Int, Int> {
return value
}
- override fun allocationSize(value: Int) = 4
+ override fun allocationSize(value: Int) = 4UL
override fun write(value: Int, buf: ByteBuffer) {
buf.putInt(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
index 601cfc7c2c..35cf8f3169 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterLong: FfiConverter<Long, Long> {
return value
}
- override fun allocationSize(value: Long) = 8
+ override fun allocationSize(value: Long) = 8UL
override fun write(value: Long, buf: ByteBuffer) {
buf.putLong(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
index 9237768dbf..27c98a6659 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterByte: FfiConverter<Byte, Byte> {
return value
}
- override fun allocationSize(value: Byte) = 1
+ override fun allocationSize(value: Byte) = 1UL
override fun write(value: Byte, buf: ByteBuffer) {
buf.put(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt
new file mode 100644
index 0000000000..0b4249fb11
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt
@@ -0,0 +1,14 @@
+{%- call kt::docstring_value(interface_docstring, 0) %}
+public interface {{ interface_name }} {
+ {% for meth in methods.iter() -%}
+ {%- call kt::docstring(meth, 4) %}
+ {% if meth.is_async() -%}suspend {% endif -%}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list(meth, true) %})
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+ companion object
+}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
index 776c402727..a80418eb00 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
@@ -12,8 +12,8 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_n
}
}
- override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int {
- val spaceForMapSize = 4
+ override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): ULong {
+ val spaceForMapSize = 4UL
val spaceForChildren = value.map { (k, v) ->
{{ key_type|allocation_size_fn }}(k) +
{{ value_type|allocation_size_fn }}(v)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
index 6a3aeada35..1bac8a435c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
@@ -13,14 +13,57 @@ private inline fun <reified Lib : Library> loadIndirect(
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
}
+// Define FFI callback types
+{%- for def in ci.ffi_definitions() %}
+{%- match def %}
+{%- when FfiDefinition::CallbackFunction(callback) %}
+internal interface {{ callback.name()|ffi_callback_name }} : com.sun.jna.Callback {
+ fun callback(
+ {%- for arg in callback.arguments() -%}
+ {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }},
+ {%- endfor -%}
+ {%- if callback.has_rust_call_status_arg() -%}
+ uniffiCallStatus: UniffiRustCallStatus,
+ {%- endif -%}
+ )
+ {%- match callback.return_type() %}
+ {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }}
+ {%- when None %}
+ {%- endmatch %}
+}
+{%- when FfiDefinition::Struct(ffi_struct) %}
+@Structure.FieldOrder({% for field in ffi_struct.fields() %}"{{ field.name()|var_name_raw }}"{% if !loop.last %}, {% endif %}{% endfor %})
+internal open class {{ ffi_struct.name()|ffi_struct_name }}(
+ {%- for field in ffi_struct.fields() %}
+ @JvmField internal var {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }},
+ {%- endfor %}
+) : Structure() {
+ class UniffiByValue(
+ {%- for field in ffi_struct.fields() %}
+ {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }},
+ {%- endfor %}
+ ): {{ ffi_struct.name()|ffi_struct_name }}({%- for field in ffi_struct.fields() %}{{ field.name()|var_name }}, {%- endfor %}), Structure.ByValue
+
+ internal fun uniffiSetValue(other: {{ ffi_struct.name()|ffi_struct_name }}) {
+ {%- for field in ffi_struct.fields() %}
+ {{ field.name()|var_name }} = other.{{ field.name()|var_name }}
+ {%- endfor %}
+ }
+
+}
+{%- when FfiDefinition::Function(_) %}
+{# functions are handled below #}
+{%- endmatch %}
+{%- endfor %}
+
// A JNA Library to expose the extern-C FFI definitions.
// This is an implementation detail which will be called internally by the public API.
-internal interface _UniFFILib : Library {
+internal interface UniffiLib : Library {
companion object {
- internal val INSTANCE: _UniFFILib by lazy {
- loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}")
- .also { lib: _UniFFILib ->
+ internal val INSTANCE: UniffiLib by lazy {
+ loadIndirect<UniffiLib>(componentName = "{{ ci.namespace() }}")
+ .also { lib: UniffiLib ->
uniffiCheckContractApiVersion(lib)
uniffiCheckApiChecksums(lib)
{% for fn in self.initialization_fns() -%}
@@ -28,6 +71,12 @@ internal interface _UniFFILib : Library {
{% endfor -%}
}
}
+ {% if ci.contains_object_types() %}
+ // The Cleaner for the whole library
+ internal val CLEANER: UniffiCleaner by lazy {
+ UniffiCleaner.create()
+ }
+ {%- endif %}
}
{% for func in ci.iter_ffi_function_definitions() -%}
@@ -37,7 +86,7 @@ internal interface _UniFFILib : Library {
{% endfor %}
}
-private fun uniffiCheckContractApiVersion(lib: _UniFFILib) {
+private fun uniffiCheckContractApiVersion(lib: UniffiLib) {
// Get the bindings contract version from our ComponentInterface
val bindings_contract_version = {{ ci.uniffi_contract_version() }}
// Get the scaffolding contract version by calling the into the dylib
@@ -48,7 +97,7 @@ private fun uniffiCheckContractApiVersion(lib: _UniFFILib) {
}
@Suppress("UNUSED_PARAMETER")
-private fun uniffiCheckApiChecksums(lib: _UniFFILib) {
+private fun uniffiCheckApiChecksums(lib: UniffiLib) {
{%- for (name, expected_checksum) in ci.iter_checksums() %}
if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt
new file mode 100644
index 0000000000..e3e85544d7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt
@@ -0,0 +1,40 @@
+
+// The cleaner interface for Object finalization code to run.
+// This is the entry point to any implementation that we're using.
+//
+// The cleaner registers objects and returns cleanables, so now we are
+// defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the
+// different implmentations available at compile time.
+interface UniffiCleaner {
+ interface Cleanable {
+ fun clean()
+ }
+
+ fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable
+
+ companion object
+}
+
+// The fallback Jna cleaner, which is available for both Android, and the JVM.
+private class UniffiJnaCleaner : UniffiCleaner {
+ private val cleaner = com.sun.jna.internal.Cleaner.getCleaner()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ UniffiJnaCleanable(cleaner.register(value, cleanUpTask))
+}
+
+private class UniffiJnaCleanable(
+ private val cleanable: com.sun.jna.internal.Cleaner.Cleanable,
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
+
+// We decide at uniffi binding generation time whether we were
+// using Android or not.
+// There are further runtime checks to chose the correct implementation
+// of the cleaner.
+{% if config.android_cleaner() %}
+{%- include "ObjectCleanerHelperAndroid.kt" %}
+{%- else %}
+{%- include "ObjectCleanerHelperJvm.kt" %}
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt
new file mode 100644
index 0000000000..d025879848
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt
@@ -0,0 +1,26 @@
+{{- self.add_import("android.os.Build") }}
+{{- self.add_import("androidx.annotation.RequiresApi") }}
+
+private fun UniffiCleaner.Companion.create(): UniffiCleaner =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ AndroidSystemCleaner()
+ } else {
+ UniffiJnaCleaner()
+ }
+
+// The SystemCleaner, available from API Level 33.
+// Some API Level 33 OSes do not support using it, so we require API Level 34.
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+private class AndroidSystemCleaner : UniffiCleaner {
+ val cleaner = android.system.SystemCleaner.cleaner()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ AndroidSystemCleanable(cleaner.register(value, cleanUpTask))
+}
+
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+private class AndroidSystemCleanable(
+ private val cleanable: java.lang.ref.Cleaner.Cleanable,
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt
new file mode 100644
index 0000000000..c43bc167fc
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt
@@ -0,0 +1,25 @@
+private fun UniffiCleaner.Companion.create(): UniffiCleaner =
+ try {
+ // For safety's sake: if the library hasn't been run in android_cleaner = true
+ // mode, but is being run on Android, then we still need to think about
+ // Android API versions.
+ // So we check if java.lang.ref.Cleaner is there, and use that…
+ java.lang.Class.forName("java.lang.ref.Cleaner")
+ JavaLangRefCleaner()
+ } catch (e: ClassNotFoundException) {
+ // … otherwise, fallback to the JNA cleaner.
+ UniffiJnaCleaner()
+ }
+
+private class JavaLangRefCleaner : UniffiCleaner {
+ val cleaner = java.lang.ref.Cleaner.create()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ JavaLangRefCleanable(cleaner.register(value, cleanUpTask))
+}
+
+private class JavaLangRefCleanable(
+ val cleanable: java.lang.ref.Cleaner.Cleanable
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
deleted file mode 100644
index b9352c690f..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-// Interface implemented by anything that can contain an object reference.
-//
-// Such types expose a `destroy()` method that must be called to cleanly
-// dispose of the contained objects. Failure to call this method may result
-// in memory leaks.
-//
-// The easiest way to ensure this method is called is to use the `.use`
-// helper method to execute a block and destroy the object at the end.
-interface Disposable {
- fun destroy()
- companion object {
- fun destroy(vararg args: Any?) {
- args.filterIsInstance<Disposable>()
- .forEach(Disposable::destroy)
- }
- }
-}
-
-inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
- try {
- block(this)
- } finally {
- try {
- // N.B. our implementation is on the nullable type `Disposable?`.
- this?.destroy()
- } catch (e: Throwable) {
- // swallow
- }
- }
-
-// The base class for all UniFFI Object types.
-//
-// This class provides core operations for working with the Rust `Arc<T>` pointer to
-// the live Rust struct on the other side of the FFI.
-//
-// There's some subtlety here, because we have to be careful not to operate on a Rust
-// struct after it has been dropped, and because we must expose a public API for freeing
-// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
-//
-// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
-// Method calls need to read this pointer from the object's state and pass it in to
-// the Rust FFI.
-//
-// * When an `FFIObject` is no longer needed, its pointer should be passed to a
-// special destructor function provided by the Rust FFI, which will drop the
-// underlying Rust struct.
-//
-// * Given an `FFIObject` instance, calling code is expected to call the special
-// `destroy` method in order to free it after use, either by calling it explicitly
-// or by using a higher-level helper like the `use` method. Failing to do so will
-// leak the underlying Rust struct.
-//
-// * We can't assume that calling code will do the right thing, and must be prepared
-// to handle Kotlin method calls executing concurrently with or even after a call to
-// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
-//
-// * We must never allow Rust code to operate on the underlying Rust struct after
-// the destructor has been called, and must never call the destructor more than once.
-// Doing so may trigger memory unsafety.
-//
-// If we try to implement this with mutual exclusion on access to the pointer, there is the
-// possibility of a race between a method call and a concurrent call to `destroy`:
-//
-// * Thread A starts a method call, reads the value of the pointer, but is interrupted
-// before it can pass the pointer over the FFI to Rust.
-// * Thread B calls `destroy` and frees the underlying Rust struct.
-// * Thread A resumes, passing the already-read pointer value to Rust and triggering
-// a use-after-free.
-//
-// One possible solution would be to use a `ReadWriteLock`, with each method call taking
-// a read lock (and thus allowed to run concurrently) and the special `destroy` method
-// taking a write lock (and thus blocking on live method calls). However, we aim not to
-// generate methods with any hidden blocking semantics, and a `destroy` method that might
-// block if called incorrectly seems to meet that bar.
-//
-// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track
-// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
-// has been called. These are updated according to the following rules:
-//
-// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
-// The initial value for the flag is false.
-//
-// * At the start of each method call, we atomically check the counter.
-// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
-// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
-//
-// * At the end of each method call, we atomically decrement and check the counter.
-// If it has reached zero then we destroy the underlying Rust struct.
-//
-// * When `destroy` is called, we atomically flip the flag from false to true.
-// If the flag was already true we silently fail.
-// Otherwise we atomically decrement and check the counter.
-// If it has reached zero then we destroy the underlying Rust struct.
-//
-// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
-// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
-//
-// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
-// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
-// of the underlying Rust code.
-//
-// In the future we may be able to replace some of this with automatic finalization logic, such as using
-// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
-// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
-// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
-// so there would still be some complexity here).
-//
-// Sigh...all of this for want of a robust finalization mechanism.
-//
-// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
-//
-abstract class FFIObject(
- protected val pointer: Pointer
-): Disposable, AutoCloseable {
-
- private val wasDestroyed = AtomicBoolean(false)
- private val callCounter = AtomicLong(1)
-
- open protected fun freeRustArcPtr() {
- // To be overridden in subclasses.
- }
-
- override fun destroy() {
- // Only allow a single call to this method.
- // TODO: maybe we should log a warning if called more than once?
- if (this.wasDestroyed.compareAndSet(false, true)) {
- // This decrement always matches the initial count of 1 given at creation time.
- if (this.callCounter.decrementAndGet() == 0L) {
- this.freeRustArcPtr()
- }
- }
- }
-
- @Synchronized
- override fun close() {
- this.destroy()
- }
-
- internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
- // Check and increment the call counter, to keep the object alive.
- // This needs a compare-and-set retry loop in case of concurrent updates.
- do {
- val c = this.callCounter.get()
- if (c == 0L) {
- throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
- }
- if (c == Long.MAX_VALUE) {
- throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
- }
- } while (! this.callCounter.compareAndSet(c, c + 1L))
- // Now we can safely do the method call without the pointer being freed concurrently.
- try {
- return block(this.pointer)
- } finally {
- // This decrement always matches the increment we performed above.
- if (this.callCounter.decrementAndGet() == 0L) {
- this.freeRustArcPtr()
- }
- }
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
index 8ce27a5d04..62cac7a4d0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
@@ -1,125 +1,282 @@
+// This template implements a class for working with a Rust struct via a Pointer/Arc<T>
+// to the live Rust struct on the other side of the FFI.
+//
+// Each instance implements core operations for working with the Rust `Arc<T>` and the
+// Kotlin Pointer to work with the live Rust struct on the other side of the FFI.
+//
+// There's some subtlety here, because we have to be careful not to operate on a Rust
+// struct after it has been dropped, and because we must expose a public API for freeing
+// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
+//
+// * Each instance holds an opaque pointer to the underlying Rust struct.
+// Method calls need to read this pointer from the object's state and pass it in to
+// the Rust FFI.
+//
+// * When an instance is no longer needed, its pointer should be passed to a
+// special destructor function provided by the Rust FFI, which will drop the
+// underlying Rust struct.
+//
+// * Given an instance, calling code is expected to call the special
+// `destroy` method in order to free it after use, either by calling it explicitly
+// or by using a higher-level helper like the `use` method. Failing to do so risks
+// leaking the underlying Rust struct.
+//
+// * We can't assume that calling code will do the right thing, and must be prepared
+// to handle Kotlin method calls executing concurrently with or even after a call to
+// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
+//
+// * We must never allow Rust code to operate on the underlying Rust struct after
+// the destructor has been called, and must never call the destructor more than once.
+// Doing so may trigger memory unsafety.
+//
+// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
+// is implemented to call the destructor when the Kotlin object becomes unreachable.
+// This is done in a background thread. This is not a panacea, and client code should be aware that
+// 1. the thread may starve if some there are objects that have poorly performing
+// `drop` methods or do significant work in their `drop` methods.
+// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
+// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
+//
+// If we try to implement this with mutual exclusion on access to the pointer, there is the
+// possibility of a race between a method call and a concurrent call to `destroy`:
+//
+// * Thread A starts a method call, reads the value of the pointer, but is interrupted
+// before it can pass the pointer over the FFI to Rust.
+// * Thread B calls `destroy` and frees the underlying Rust struct.
+// * Thread A resumes, passing the already-read pointer value to Rust and triggering
+// a use-after-free.
+//
+// One possible solution would be to use a `ReadWriteLock`, with each method call taking
+// a read lock (and thus allowed to run concurrently) and the special `destroy` method
+// taking a write lock (and thus blocking on live method calls). However, we aim not to
+// generate methods with any hidden blocking semantics, and a `destroy` method that might
+// block if called incorrectly seems to meet that bar.
+//
+// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
+// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
+// has been called. These are updated according to the following rules:
+//
+// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
+// The initial value for the flag is false.
+//
+// * At the start of each method call, we atomically check the counter.
+// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
+// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
+//
+// * At the end of each method call, we atomically decrement and check the counter.
+// If it has reached zero then we destroy the underlying Rust struct.
+//
+// * When `destroy` is called, we atomically flip the flag from false to true.
+// If the flag was already true we silently fail.
+// Otherwise we atomically decrement and check the counter.
+// If it has reached zero then we destroy the underlying Rust struct.
+//
+// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
+// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
+//
+// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
+// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
+// of the underlying Rust code.
+//
+// This makes a cleaner a better alternative to _not_ calling `destroy()` as
+// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
+// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
+// thread may be starved, and the app will leak memory.
+//
+// In this case, `destroy`ing manually may be a better solution.
+//
+// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
+// with Rust peers are reclaimed:
+//
+// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
+// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
+// 3. The memory is reclaimed when the process terminates.
+//
+// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
+//
+
+{{ self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+{%- if self.include_once_check("interface-support") %}
+ {%- include "ObjectCleanerHelper.kt" %}
+{%- endif %}
+
{%- let obj = ci|get_object_definition(name) %}
-{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %}
-{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
-{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+{%- let (interface_name, impl_class_name) = obj|object_names(ci) %}
+{%- let methods = obj.methods() %}
+{%- let interface_docstring = obj.docstring() %}
+{%- let is_error = ci.is_name_used_as_error(name) %}
+{%- let ffi_converter_name = obj|ffi_converter_name %}
-public interface {{ type_name }}Interface {
- {% for meth in obj.methods() -%}
- {%- match meth.throws_type() -%}
- {%- when Some with (throwable) -%}
- @Throws({{ throwable|type_name(ci) }}::class)
- {%- when None -%}
- {%- endmatch %}
- {% if meth.is_async() -%}
- suspend fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- else -%}
- fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- endif %}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
- {%- when None -%}
- {%- endmatch -%}
+{%- include "Interface.kt" %}
- {% endfor %}
- companion object
-}
+{%- call kt::docstring(obj, 0) %}
+{% if (is_error) %}
+open class {{ impl_class_name }} : Exception, Disposable, AutoCloseable, {{ interface_name }} {
+{% else -%}
+open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }} {
+{%- endif %}
-class {{ type_name }}(
- pointer: Pointer
-) : FFIObject(pointer), {{ type_name }}Interface {
+ constructor(pointer: Pointer) {
+ this.pointer = pointer
+ this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer))
+ }
+
+ /**
+ * This constructor can be used to instantiate a fake object. Only used for tests. Any
+ * attempt to actually use an object constructed this way will fail as there is no
+ * connected Rust object.
+ */
+ @Suppress("UNUSED_PARAMETER")
+ constructor(noPointer: NoPointer) {
+ this.pointer = null
+ this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer))
+ }
{%- match obj.primary_constructor() %}
- {%- when Some with (cons) %}
- constructor({% call kt::arg_list_decl(cons) -%}) :
+ {%- when Some(cons) %}
+ {%- if cons.is_async() %}
+ // Note no constructor generated for this object as it is async.
+ {%- else %}
+ {%- call kt::docstring(cons, 4) %}
+ constructor({% call kt::arg_list(cons, true) -%}) :
this({% call kt::to_ffi_call(cons) %})
+ {%- endif %}
{%- when None %}
{%- endmatch %}
- /**
- * Disconnect the object from the underlying Rust object.
- *
- * It can be called more than once, but once called, interacting with the object
- * causes an `IllegalStateException`.
- *
- * Clients **must** call this method once done with the object, or cause a memory leak.
- */
- override protected fun freeRustArcPtr() {
- rustCall() { status ->
- _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status)
+ protected val pointer: Pointer?
+ protected val cleanable: UniffiCleaner.Cleanable
+
+ private val wasDestroyed = AtomicBoolean(false)
+ private val callCounter = AtomicLong(1)
+
+ override fun destroy() {
+ // Only allow a single call to this method.
+ // TODO: maybe we should log a warning if called more than once?
+ if (this.wasDestroyed.compareAndSet(false, true)) {
+ // This decrement always matches the initial count of 1 given at creation time.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ cleanable.clean()
+ }
}
}
- {% for meth in obj.methods() -%}
- {%- match meth.throws_type() -%}
- {%- when Some with (throwable) %}
- @Throws({{ throwable|type_name(ci) }}::class)
- {%- else -%}
- {%- endmatch -%}
- {%- if meth.is_async() %}
- @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
- override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
- return uniffiRustCallAsync(
- callWithPointer { thisPtr ->
- _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}(
- thisPtr,
- {% call kt::arg_list_lowered(meth) %}
- )
- },
- {{ meth|async_poll(ci) }},
- {{ meth|async_complete(ci) }},
- {{ meth|async_free(ci) }},
- // lift function
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- { {{ return_type|lift_fn }}(it) },
- {%- when None %}
- { Unit },
- {% endmatch %}
- // Error FFI converter
- {%- match meth.throws_type() %}
- {%- when Some(e) %}
- {{ e|type_name(ci) }}.ErrorHandler,
- {%- when None %}
- NullCallStatusErrorHandler,
- {%- endmatch %}
- )
- }
- {%- else -%}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) -%}
- override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} =
- callWithPointer {
- {%- call kt::to_ffi_call_with_prefix("it", meth) %}
- }.let {
- {{ return_type|lift_fn }}(it)
+ @Synchronized
+ override fun close() {
+ this.destroy()
+ }
+
+ internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
+ // Check and increment the call counter, to keep the object alive.
+ // This needs a compare-and-set retry loop in case of concurrent updates.
+ do {
+ val c = this.callCounter.get()
+ if (c == 0L) {
+ throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
+ }
+ if (c == Long.MAX_VALUE) {
+ throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
+ }
+ } while (! this.callCounter.compareAndSet(c, c + 1L))
+ // Now we can safely do the method call without the pointer being freed concurrently.
+ try {
+ return block(this.uniffiClonePointer())
+ } finally {
+ // This decrement always matches the increment we performed above.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ cleanable.clean()
+ }
}
+ }
- {%- when None -%}
- override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) =
- callWithPointer {
- {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ // Use a static inner class instead of a closure so as not to accidentally
+ // capture `this` as part of the cleanable's action.
+ private class UniffiCleanAction(private val pointer: Pointer?) : Runnable {
+ override fun run() {
+ pointer?.let { ptr ->
+ uniffiRustCall { status ->
+ UniffiLib.INSTANCE.{{ obj.ffi_object_free().name() }}(ptr, status)
+ }
+ }
}
- {% endmatch %}
- {% endif %}
+ }
+
+ fun uniffiClonePointer(): Pointer {
+ return uniffiRustCall() { status ->
+ UniffiLib.INSTANCE.{{ obj.ffi_object_clone().name() }}(pointer!!, status)
+ }
+ }
+
+ {% for meth in obj.methods() -%}
+ {%- call kt::func_decl("override", meth, 4) %}
{% endfor %}
+ {%- for tm in obj.uniffi_traits() %}
+ {%- match tm %}
+ {% when UniffiTrait::Display { fmt } %}
+ override fun toString(): String {
+ return {{ fmt.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(fmt) %})
+ }
+ {% when UniffiTrait::Eq { eq, ne } %}
+ {# only equals used #}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is {{ impl_class_name}}) return false
+ return {{ eq.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(eq) %})
+ }
+ {% when UniffiTrait::Hash { hash } %}
+ override fun hashCode(): Int {
+ return {{ hash.return_type().unwrap()|lift_fn }}({%- call kt::to_ffi_call(hash) %}).toInt()
+ }
+ {%- else %}
+ {%- endmatch %}
+ {%- endfor %}
+
+ {# XXX - "companion object" confusion? How to have alternate constructors *and* be an error? #}
{% if !obj.alternate_constructors().is_empty() -%}
companion object {
{% for cons in obj.alternate_constructors() -%}
- fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ type_name }} =
- {{ type_name }}({% call kt::to_ffi_call(cons) %})
+ {% call kt::func_decl("", cons, 4) %}
{% endfor %}
}
+ {% else if is_error %}
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ impl_class_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ impl_class_name }} {
+ // Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer.
+ val bb = error_buf.asByteBuffer()
+ if (bb == null) {
+ throw InternalException("?")
+ }
+ return {{ ffi_converter_name }}.read(bb)
+ }
+ }
{% else %}
companion object
{% endif %}
}
-public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
- override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it }
+{%- if obj.has_callback_interface() %}
+{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %}
+{%- let vtable_methods = obj.vtable_methods() %}
+{%- let ffi_init_callback = obj.ffi_init_callback() %}
+{% include "CallbackInterfaceImpl.kt" %}
+{%- endif %}
+
+public object {{ ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
+ {%- if obj.has_callback_interface() %}
+ internal val handleMap = UniffiHandleMap<{{ type_name }}>()
+ {%- endif %}
+
+ override fun lower(value: {{ type_name }}): Pointer {
+ {%- if obj.has_callback_interface() %}
+ return Pointer(handleMap.insert(value))
+ {%- else %}
+ return value.uniffiClonePointer()
+ {%- endif %}
+ }
override fun lift(value: Pointer): {{ type_name }} {
- return {{ type_name }}(value)
+ return {{ impl_class_name }}(value)
}
override fun read(buf: ByteBuffer): {{ type_name }} {
@@ -128,7 +285,7 @@ public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointe
return lift(Pointer(buf.getLong()))
}
- override fun allocationSize(value: {{ type_name }}) = 8
+ override fun allocationSize(value: {{ type_name }}) = 8UL
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
// The Rust code always expects pointers written as 8 bytes,
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
index 56cb5f87a5..98451e1451 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
@@ -8,11 +8,11 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_nam
return {{ inner_type|read_fn }}(buf)
}
- override fun allocationSize(value: {{ inner_type_name }}?): Int {
+ override fun allocationSize(value: {{ inner_type_name }}?): ULong {
if (value == null) {
- return 1
+ return 1UL
} else {
- return 1 + {{ inner_type|allocation_size_fn }}(value)
+ return 1UL + {{ inner_type|allocation_size_fn }}(value)
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md
new file mode 100644
index 0000000000..0e770cb829
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md
@@ -0,0 +1,13 @@
+# Rules for the Kotlin template code
+
+## Naming
+
+Private variables, classes, functions, etc. should be prefixed with `uniffi`, `Uniffi`, or `UNIFFI`.
+This avoids naming collisions with user-defined items.
+Users will not get name collisions as long as they don't use "uniffi", which is reserved for us.
+
+In particular, make sure to use the `uniffi` prefix for any variable names in generated functions.
+If you name a variable something like `result` the code will probably work initially.
+Then it will break later on when a user decides to define a function with a parameter named `result`.
+
+Note: this doesn't apply to items that we want to expose, for example users may want to catch `InternalException` so doesn't get the `Uniffi` prefix.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
index b588ca1398..bc3028c736 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
@@ -1,8 +1,11 @@
{%- let rec = ci|get_record_definition(name) %}
+{%- if rec.has_fields() %}
+{%- call kt::docstring(rec, 0) %}
data class {{ type_name }} (
{%- for field in rec.fields() %}
- var {{ field.name()|var_name }}: {{ field|type_name(ci) -}}
+ {%- call kt::docstring(field, 4) %}
+ {% if config.generate_immutable_records() %}val{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name(ci) -}}
{%- match field.default_value() %}
{%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }}
{%- else %}
@@ -18,21 +21,39 @@ data class {{ type_name }} (
{% endif %}
companion object
}
+{%- else -%}
+{%- call kt::docstring(rec, 0) %}
+class {{ type_name }} {
+ override fun equals(other: Any?): Boolean {
+ return other is {{ type_name }}
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+
+ companion object
+}
+{%- endif %}
public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
override fun read(buf: ByteBuffer): {{ type_name }} {
+ {%- if rec.has_fields() %}
return {{ type_name }}(
{%- for field in rec.fields() %}
{{ field|read_fn }}(buf),
{%- endfor %}
)
+ {%- else %}
+ return {{ type_name }}()
+ {%- endif %}
}
- override fun allocationSize(value: {{ type_name }}) = (
+ override fun allocationSize(value: {{ type_name }}) = {%- if rec.has_fields() %} (
{%- for field in rec.fields() %}
- {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif %}
{%- endfor %}
- )
+ ) {%- else %} 0UL {%- endif %}
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
{%- for field in rec.fields() %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
index dfbea24074..b28f25bfc3 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
@@ -4,32 +4,41 @@
@Structure.FieldOrder("capacity", "len", "data")
open class RustBuffer : Structure() {
- @JvmField var capacity: Int = 0
- @JvmField var len: Int = 0
+ // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values.
+ // When dealing with these fields, make sure to call `toULong()`.
+ @JvmField var capacity: Long = 0
+ @JvmField var len: Long = 0
@JvmField var data: Pointer? = null
class ByValue: RustBuffer(), Structure.ByValue
class ByReference: RustBuffer(), Structure.ByReference
+ internal fun setValue(other: RustBuffer) {
+ capacity = other.capacity
+ len = other.len
+ data = other.data
+ }
+
companion object {
- internal fun alloc(size: Int = 0) = rustCall() { status ->
- _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status)
+ internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status ->
+ // Note: need to convert the size to a `Long` value to make this work with JVM.
+ UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toLong(), status)
}.also {
if(it.data == null) {
throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
}
}
- internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue {
+ internal fun create(capacity: ULong, len: ULong, data: Pointer?): RustBuffer.ByValue {
var buf = RustBuffer.ByValue()
- buf.capacity = capacity
- buf.len = len
+ buf.capacity = capacity.toLong()
+ buf.len = len.toLong()
buf.data = data
return buf
}
- internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
- _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
+ internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status ->
+ UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
}
}
@@ -53,9 +62,9 @@ class RustBufferByReference : ByReference(16) {
fun setValue(value: RustBuffer.ByValue) {
// NOTE: The offsets are as they are in the C-like struct.
val pointer = getPointer()
- pointer.setInt(0, value.capacity)
- pointer.setInt(4, value.len)
- pointer.setPointer(8, value.data)
+ pointer.setLong(0, value.capacity)
+ pointer.setLong(8, value.len)
+ pointer.setPointer(16, value.data)
}
/**
@@ -64,9 +73,9 @@ class RustBufferByReference : ByReference(16) {
fun getValue(): RustBuffer.ByValue {
val pointer = getPointer()
val value = RustBuffer.ByValue()
- value.writeField("capacity", pointer.getInt(0))
- value.writeField("len", pointer.getInt(4))
- value.writeField("data", pointer.getPointer(8))
+ value.writeField("capacity", pointer.getLong(0))
+ value.writeField("len", pointer.getLong(8))
+ value.writeField("data", pointer.getLong(16))
return value
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
index 876d1bc05e..61f911cb0c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
@@ -8,15 +8,15 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_typ
}
}
- override fun allocationSize(value: List<{{ inner_type_name }}>): Int {
- val sizeForLength = 4
+ override fun allocationSize(value: List<{{ inner_type_name }}>): ULong {
+ val sizeForLength = 4UL
val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum()
return sizeForLength + sizeForItems
}
override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) {
buf.putInt(value.size)
- value.forEach {
+ value.iterator().forEach {
{{ inner_type|write_fn }}(it, buf)
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
index 68324be4f9..b67435bd1a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
@@ -4,7 +4,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
// store our length and avoid writing it out to the buffer.
override fun lift(value: RustBuffer.ByValue): String {
try {
- val byteArr = ByteArray(value.len)
+ val byteArr = ByteArray(value.len.toInt())
value.asByteBuffer()!!.get(byteArr)
return byteArr.toString(Charsets.UTF_8)
} finally {
@@ -31,7 +31,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
val byteBuf = toUtf8(value)
// Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
// to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
- val rbuf = RustBuffer.alloc(byteBuf.limit())
+ val rbuf = RustBuffer.alloc(byteBuf.limit().toULong())
rbuf.asByteBuffer()!!.put(byteBuf)
return rbuf
}
@@ -39,9 +39,9 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
// We aren't sure exactly how many bytes our string will be once it's UTF-8
// encoded. Allocate 3 bytes per UTF-16 code unit which will always be
// enough.
- override fun allocationSize(value: String): Int {
- val sizeForLength = 4
- val sizeForString = value.length * 3
+ override fun allocationSize(value: String): ULong {
+ val sizeForLength = 4UL
+ val sizeForString = value.length.toULong() * 3UL
return sizeForLength + sizeForString
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
index 21069d7ce8..10a450a4bd 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
@@ -14,7 +14,7 @@ public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> {
}
// 8 bytes for seconds, 4 bytes for nanoseconds
- override fun allocationSize(value: java.time.Instant) = 12
+ override fun allocationSize(value: java.time.Instant) = 12UL
override fun write(value: java.time.Instant, buf: ByteBuffer) {
var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
index 6a841d3484..681c48093a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
@@ -1,51 +1 @@
-{%- if func.is_async() %}
-{%- match func.throws_type() -%}
-{%- when Some with (throwable) %}
-@Throws({{ throwable|type_name(ci) }}::class)
-{%- else -%}
-{%- endmatch %}
-
-@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
-suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
- return uniffiRustCallAsync(
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}),
- {{ func|async_poll(ci) }},
- {{ func|async_complete(ci) }},
- {{ func|async_free(ci) }},
- // lift function
- {%- match func.return_type() %}
- {%- when Some(return_type) %}
- { {{ return_type|lift_fn }}(it) },
- {%- when None %}
- { Unit },
- {% endmatch %}
- // Error FFI converter
- {%- match func.throws_type() %}
- {%- when Some(e) %}
- {{ e|type_name(ci) }}.ErrorHandler,
- {%- when None %}
- NullCallStatusErrorHandler,
- {%- endmatch %}
- )
-}
-
-{%- else %}
-{%- match func.throws_type() -%}
-{%- when Some with (throwable) %}
-@Throws({{ throwable|type_name(ci) }}::class)
-{%- else -%}
-{%- endmatch -%}
-
-{%- match func.return_type() -%}
-{%- when Some with (return_type) %}
-
-fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} {
- return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %})
-}
-{% when None %}
-
-fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) =
- {% call kt::to_ffi_call(func) %}
-
-{% endmatch %}
-{%- endif %}
+{%- call kt::func_decl("", func, 8) %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
index 103d444ea3..c27121b701 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
@@ -1,5 +1,38 @@
{%- import "macros.kt" as kt %}
+// Interface implemented by anything that can contain an object reference.
+//
+// Such types expose a `destroy()` method that must be called to cleanly
+// dispose of the contained objects. Failure to call this method may result
+// in memory leaks.
+//
+// The easiest way to ensure this method is called is to use the `.use`
+// helper method to execute a block and destroy the object at the end.
+interface Disposable {
+ fun destroy()
+ companion object {
+ fun destroy(vararg args: Any?) {
+ args.filterIsInstance<Disposable>()
+ .forEach(Disposable::destroy)
+ }
+ }
+}
+
+inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
+ try {
+ block(this)
+ } finally {
+ try {
+ // N.B. our implementation is on the nullable type `Disposable?`.
+ this?.destroy()
+ } catch (e: Throwable) {
+ // swallow
+ }
+ }
+
+/** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */
+object NoPointer
+
{%- for type_ in ci.iter_types() %}
{%- let type_name = type_|type_name(ci) %}
{%- let ffi_converter_name = type_|ffi_converter_name %}
@@ -82,9 +115,6 @@
{%- when Type::CallbackInterface { module_path, name } %}
{% include "CallbackInterfaceTemplate.kt" %}
-{%- when Type::ForeignExecutor %}
-{% include "ForeignExecutorTemplate.kt" %}
-
{%- when Type::Timestamp %}
{% include "TimestampHelper.kt" %}
@@ -104,6 +134,10 @@
{%- if ci.has_async_fns() %}
{# Import types needed for async support #}
{{ self.add_import("kotlin.coroutines.resume") }}
+{{ self.add_import("kotlinx.coroutines.launch") }}
{{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }}
{{ self.add_import("kotlinx.coroutines.CancellableContinuation") }}
+{{ self.add_import("kotlinx.coroutines.DelicateCoroutinesApi") }}
+{{ self.add_import("kotlinx.coroutines.Job") }}
+{{ self.add_import("kotlinx.coroutines.GlobalScope") }}
{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
index 279a8fa91b..b179145b62 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUShort: FfiConverter<UShort, Short> {
return value.toShort()
}
- override fun allocationSize(value: UShort) = 2
+ override fun allocationSize(value: UShort) = 2UL
override fun write(value: UShort, buf: ByteBuffer) {
buf.putShort(value.toShort())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
index da7b5b28d6..202d5bcd5b 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUInt: FfiConverter<UInt, Int> {
return value.toInt()
}
- override fun allocationSize(value: UInt) = 4
+ override fun allocationSize(value: UInt) = 4UL
override fun write(value: UInt, buf: ByteBuffer) {
buf.putInt(value.toInt())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
index 44d27ad36b..9be2a5a69d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterULong: FfiConverter<ULong, Long> {
return value.toLong()
}
- override fun allocationSize(value: ULong) = 8
+ override fun allocationSize(value: ULong) = 8UL
override fun write(value: ULong, buf: ByteBuffer) {
buf.putLong(value.toLong())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
index b6d176603e..ee360673e0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUByte: FfiConverter<UByte, Byte> {
return value.toByte()
}
- override fun allocationSize(value: UByte) = 1
+ override fun allocationSize(value: UByte) = 1UL
override fun write(value: UByte, buf: ByteBuffer) {
buf.put(value.toByte())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
index 6a95d6a66d..7acfdc8861 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
@@ -1,32 +1,91 @@
{#
// Template to call into rust. Used in several places.
-// Variable names in `arg_list_decl` should match up with arg lists
+// Variable names in `arg_list` should match up with arg lists
// passed to rust via `arg_list_lowered`
#}
{%- macro to_ffi_call(func) -%}
- {%- match func.throws_type() %}
- {%- when Some with (e) %}
- rustCallWithError({{ e|type_name(ci) }})
- {%- else %}
- rustCall()
- {%- endmatch %} { _status ->
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} _status)
-}
-{%- endmacro -%}
+ {%- if func.takes_self() %}
+ callWithPointer {
+ {%- call to_raw_ffi_call(func) %}
+ }
+ {% else %}
+ {%- call to_raw_ffi_call(func) %}
+ {% endif %}
+{%- endmacro %}
-{%- macro to_ffi_call_with_prefix(prefix, func) %}
+{%- macro to_raw_ffi_call(func) -%}
{%- match func.throws_type() %}
{%- when Some with (e) %}
- rustCallWithError({{ e|type_name(ci) }})
+ uniffiRustCallWithError({{ e|type_name(ci) }})
{%- else %}
- rustCall()
+ uniffiRustCall()
{%- endmatch %} { _status ->
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}(
- {{- prefix }},
- {% call arg_list_lowered(func) %}
+ UniffiLib.INSTANCE.{{ func.ffi_func().name() }}(
+ {% if func.takes_self() %}it, {% endif -%}
+ {% call arg_list_lowered(func) -%}
_status)
}
+{%- endmacro -%}
+
+{%- macro func_decl(func_decl, callable, indent) %}
+ {%- call docstring(callable, indent) %}
+ {%- match callable.throws_type() -%}
+ {%- when Some(throwable) %}
+ @Throws({{ throwable|type_name(ci) }}::class)
+ {%- else -%}
+ {%- endmatch -%}
+ {%- if callable.is_async() %}
+ @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+ {{ func_decl }} suspend fun {{ callable.name()|fn_name }}(
+ {%- call arg_list(callable, !callable.takes_self()) -%}
+ ){% match callable.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
+ return {% call call_async(callable) %}
+ }
+ {%- else -%}
+ {{ func_decl }} fun {{ callable.name()|fn_name }}(
+ {%- call arg_list(callable, !callable.takes_self()) -%}
+ ){%- match callable.return_type() -%}
+ {%- when Some with (return_type) -%}
+ : {{ return_type|type_name(ci) }} {
+ return {{ return_type|lift_fn }}({% call to_ffi_call(callable) %})
+ }
+ {%- when None %}
+ = {% call to_ffi_call(callable) %}
+ {%- endmatch %}
+ {% endif %}
+{% endmacro %}
+
+{%- macro call_async(callable) -%}
+ uniffiRustCallAsync(
+{%- if callable.takes_self() %}
+ callWithPointer { thisPtr ->
+ UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}(
+ thisPtr,
+ {% call arg_list_lowered(callable) %}
+ )
+ },
+{%- else %}
+ UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}({% call arg_list_lowered(callable) %}),
+{%- endif %}
+ {{ callable|async_poll(ci) }},
+ {{ callable|async_complete(ci) }},
+ {{ callable|async_free(ci) }},
+ // lift function
+ {%- match callable.return_type() %}
+ {%- when Some(return_type) %}
+ { {{ return_type|lift_fn }}(it) },
+ {%- when None %}
+ { Unit },
+ {% endmatch %}
+ // Error FFI converter
+ {%- match callable.throws_type() %}
+ {%- when Some(e) %}
+ {{ e|type_name(ci) }}.ErrorHandler,
+ {%- when None %}
+ UniffiNullRustCallStatusErrorHandler,
+ {%- endmatch %}
+ )
{%- endmacro %}
{%- macro arg_list_lowered(func) %}
@@ -37,37 +96,42 @@
{#-
// Arglist as used in kotlin declarations of methods, functions and constructors.
+// If is_decl, then default values be specified.
// Note the var_name and type_name filters.
-#}
-{% macro arg_list_decl(func) %}
- {%- for arg in func.arguments() -%}
+{% macro arg_list(func, is_decl) %}
+{%- for arg in func.arguments() -%}
{{ arg.name()|var_name }}: {{ arg|type_name(ci) }}
- {%- match arg.default_value() %}
- {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }}
- {%- else %}
- {%- endmatch %}
- {%- if !loop.last %}, {% endif -%}
- {%- endfor %}
+{%- if is_decl %}
+{%- match arg.default_value() %}
+{%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }}
+{%- else %}
+{%- endmatch %}
+{%- endif %}
+{%- if !loop.last %}, {% endif -%}
+{%- endfor %}
{%- endmacro %}
-{% macro arg_list_protocol(func) %}
- {%- for arg in func.arguments() -%}
- {{ arg.name()|var_name }}: {{ arg|type_name(ci) }}
- {%- if !loop.last %}, {% endif -%}
- {%- endfor %}
-{%- endmacro %}
{#-
-// Arglist as used in the _UniFFILib function declarations.
+// Arglist as used in the UniffiLib function declarations.
// Note unfiltered name but ffi_type_name filters.
-#}
{%- macro arg_list_ffi_decl(func) %}
{%- for arg in func.arguments() %}
{{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value -}},
{%- endfor %}
- {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %}
+ {%- if func.has_rust_call_status_arg() %}uniffi_out_err: UniffiRustCallStatus, {% endif %}
{%- endmacro -%}
+{% macro field_name(field, field_num) %}
+{%- if field.name().is_empty() -%}
+v{{- field_num -}}
+{%- else -%}
+{{ field.name()|var_name }}
+{%- endif -%}
+{%- endmacro %}
+
// Macro for destroying fields
{%- macro destroy_fields(member) %}
Disposable.destroy(
@@ -75,3 +139,15 @@
this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%}
{% endfor -%})
{%- endmacro -%}
+
+{%- macro docstring_value(maybe_docstring, indent_spaces) %}
+{%- match maybe_docstring %}
+{%- when Some(docstring) %}
+{{ docstring|docstring(indent_spaces) }}
+{%- else %}
+{%- endmatch %}
+{%- endmacro %}
+
+{%- macro docstring(defn, indent_spaces) %}
+{%- call docstring_value(defn.docstring(), indent_spaces) %}
+{%- endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
index 9ee4229018..2cdc72a5e2 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
@@ -1,6 +1,8 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
+{%- call kt::docstring_value(ci.namespace_docstring(), 0) %}
+
@file:Suppress("NAME_SHADOWING")
package {{ config.package_name() }};
@@ -28,6 +30,7 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.CharBuffer
import java.nio.charset.CodingErrorAction
+import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.ConcurrentHashMap
{%- for req in self.imports() %}
@@ -37,6 +40,7 @@ import java.util.concurrent.ConcurrentHashMap
{% include "RustBufferTemplate.kt" %}
{% include "FfiConverterTemplate.kt" %}
{% include "Helpers.kt" %}
+{% include "HandleMap.kt" %}
// Contains loading, initialization code,
// and the FFI Function declarations in a com.sun.jna.Library.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
index 7b78540741..0824015751 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
@@ -2,10 +2,8 @@
License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-use crate::{
- bindings::{RunScriptOptions, TargetLanguage},
- library_mode::generate_bindings,
-};
+use crate::bindings::TargetLanguage;
+use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault};
use anyhow::{bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use std::env;
@@ -33,14 +31,18 @@ pub fn run_script(
args: Vec<String>,
options: &RunScriptOptions,
) -> Result<()> {
- let script_path = Utf8Path::new(".").join(script_file);
+ let script_path = Utf8Path::new(script_file);
let test_helper = UniFFITestHelper::new(crate_name)?;
- let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
+ let out_dir = test_helper.create_out_dir(tmp_dir, script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
generate_bindings(
&cdylib_path,
None,
- &[TargetLanguage::Kotlin],
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Kotlin],
+ try_format_code: false,
+ },
+ None,
&out_dir,
false,
)?;
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
index b91bcbe18f..16adeca9a5 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
@@ -3,7 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::CodeType;
-use crate::backend::{Literal, Type};
+use crate::{
+ backend::{Literal, Type},
+ bindings::python::gen_python::AsCodeType,
+};
#[derive(Debug)]
pub struct OptionalCodeType {
@@ -33,8 +36,9 @@ impl CodeType for OptionalCodeType {
fn literal(&self, literal: &Literal) -> String {
match literal {
- Literal::Null => "None".into(),
- _ => super::PythonCodeOracle.find(&self.inner).literal(literal),
+ Literal::None => "None".into(),
+ Literal::Some { inner } => super::PythonCodeOracle.find(&self.inner).literal(inner),
+ _ => panic!("Invalid literal for Optional type: {literal:?}"),
}
}
}
@@ -88,7 +92,11 @@ impl MapCodeType {
impl CodeType for MapCodeType {
fn type_label(&self) -> String {
- "dict".to_string()
+ format!(
+ "dict[{}, {}]",
+ self.key.as_codetype().type_label(),
+ self.value.as_codetype().type_label()
+ )
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs
deleted file mode 100644
index be3ba1d791..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use super::CodeType;
-
-#[derive(Debug)]
-pub struct ForeignExecutorCodeType;
-
-impl CodeType for ForeignExecutorCodeType {
- fn type_label(&self) -> String {
- "asyncio.BaseEventLoop".into()
- }
-
- fn canonical_name(&self) -> String {
- "ForeignExecutor".into()
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
index 0d19c4bb3c..0a46251d6d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
@@ -17,7 +17,7 @@ impl ExternalCodeType {
impl CodeType for ExternalCodeType {
fn type_label(&self) -> String {
- self.name.clone()
+ super::PythonCodeOracle.class_name(&self.name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
index 8178fcc102..6a10a38e7f 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
@@ -2,8 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use askama::Template;
+use camino::Utf8Path;
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
@@ -13,20 +14,43 @@ use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::Debug;
use crate::backend::TemplateExpression;
+use crate::bindings::python;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
mod callback_interface;
mod compounds;
mod custom;
mod enum_;
-mod executor;
mod external;
mod miscellany;
mod object;
mod primitives;
mod record;
+pub struct PythonBindingGenerator;
+
+impl BindingGenerator for PythonBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ python::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> {
+ if cdylib_name.is_none() {
+ bail!("Generate bindings for Python requires a cdylib, but {library_path} was given");
+ }
+ Ok(())
+ }
+}
+
/// A trait tor the implementation.
trait CodeType: Debug {
/// The language specific label used to reference this type. This will be used in
@@ -114,6 +138,8 @@ pub struct Config {
cdylib_name: Option<String>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
+ #[serde(default)]
+ external_packages: HashMap<String, String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@@ -133,6 +159,16 @@ impl Config {
"uniffi".into()
}
}
+
+ /// Get the package name for a given external namespace.
+ pub fn module_for_namespace(&self, ns: &str) -> String {
+ let ns = ns.to_string().to_snake_case();
+ match self.external_packages.get(&ns) {
+ None => format!(".{ns}"),
+ Some(value) if value.is_empty() => ns,
+ Some(value) => format!("{value}.{ns}"),
+ }
+ }
}
impl BindingsConfig for Config {
@@ -326,7 +362,19 @@ impl PythonCodeOracle {
fixup_keyword(nm.to_string().to_shouty_snake_case())
}
- fn ffi_type_label(ffi_type: &FfiType) -> String {
+ /// Get the idiomatic Python rendering of an FFI callback function name
+ fn ffi_callback_name(&self, nm: &str) -> String {
+ format!("UNIFFI_{}", nm.to_shouty_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of an FFI struct name
+ fn ffi_struct_name(&self, nm: &str) -> String {
+ // The ctypes docs use both SHOUTY_SNAKE_CASE AND UpperCamelCase for structs. Let's use
+ // UpperCamelCase and reserve shouting for global variables
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::Int8 => "ctypes.c_int8".to_string(),
FfiType::UInt8 => "ctypes.c_uint8".to_string(),
@@ -338,19 +386,64 @@ impl PythonCodeOracle {
FfiType::UInt64 => "ctypes.c_uint64".to_string(),
FfiType::Float32 => "ctypes.c_float".to_string(),
FfiType::Float64 => "ctypes.c_double".to_string(),
+ FfiType::Handle => "ctypes.c_uint64".to_string(),
FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
Some(suffix) => format!("_UniffiRustBuffer{suffix}"),
None => "_UniffiRustBuffer".to_string(),
},
+ FfiType::RustCallStatus => "_UniffiRustCallStatus".to_string(),
FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(),
- FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(),
+ FfiType::Callback(name) => self.ffi_callback_name(name),
+ FfiType::Struct(name) => self.ffi_struct_name(name),
// Pointer to an `asyncio.EventLoop` instance
- FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(),
- FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(),
- FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
- FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(),
- FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
+ FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)),
+ FfiType::VoidPointer => "ctypes.c_void_p".to_string(),
+ }
+ }
+
+ /// Default values for FFI types
+ ///
+ /// Used to set a default return value when returning an error
+ fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String {
+ match return_type {
+ Some(t) => match t {
+ FfiType::UInt8
+ | FfiType::Int8
+ | FfiType::UInt16
+ | FfiType::Int16
+ | FfiType::UInt32
+ | FfiType::Int32
+ | FfiType::UInt64
+ | FfiType::Int64 => "0".to_owned(),
+ FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(),
+ FfiType::RustArcPtr(_) => "ctypes.c_void_p()".to_owned(),
+ FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
+ Some(suffix) => format!("_UniffiRustBuffer{suffix}.default()"),
+ None => "_UniffiRustBuffer.default()".to_owned(),
+ },
+ _ => unimplemented!("FFI return type: {t:?}"),
+ },
+ // When we need to use a value for void returns, we use a `u8` placeholder
+ None => "0".to_owned(),
+ }
+ }
+
+ /// Get the name of the protocol and class name for an object.
+ ///
+ /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that.
+ /// Otherwise, the class name is the object name and the protocol name is derived from that.
+ ///
+ /// This split determines what types `FfiConverter.lower()` inputs. If we support callback
+ /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower
+ /// only lowers the concrete class.
+ fn object_names(&self, obj: &Object) -> (String, String) {
+ let class_name = self.class_name(obj.name());
+ if obj.has_callback_interface() {
+ let impl_name = format!("{class_name}Impl");
+ (class_name, impl_name)
+ } else {
+ (format!("{class_name}Protocol"), class_name)
}
}
}
@@ -392,7 +485,6 @@ impl<T: AsType> AsCodeType for T {
Type::CallbackInterface { name, .. } => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
}
- Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional { inner_type } => {
Box::new(compounds::OptionalCodeType::new(*inner_type))
}
@@ -429,6 +521,10 @@ pub mod filters {
Ok(format!("{}.lift", ffi_converter_name(as_ct)?))
}
+ pub(super) fn check_lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.check_lower", ffi_converter_name(as_ct)?))
+ }
+
pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!("{}.lower", ffi_converter_name(as_ct)?))
}
@@ -448,8 +544,18 @@ pub mod filters {
Ok(as_ct.as_codetype().literal(literal))
}
+ // Get the idiomatic Python rendering of an individual enum variant's discriminant
+ pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> {
+ let literal = e.variant_discr(*index).expect("invalid index");
+ Ok(Type::UInt64.as_codetype().literal(&literal))
+ }
+
pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
- Ok(PythonCodeOracle::ffi_type_label(type_))
+ Ok(PythonCodeOracle.ffi_type_label(type_))
+ }
+
+ pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> {
+ Ok(PythonCodeOracle.ffi_default_value(return_type.as_ref()))
}
/// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
@@ -471,4 +577,51 @@ pub mod filters {
pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.enum_variant_name(nm))
}
+
+ /// Get the idiomatic Python rendering of an FFI callback function name
+ pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(PythonCodeOracle.ffi_callback_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of an FFI struct name
+ pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(PythonCodeOracle.ffi_struct_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of an individual enum variant.
+ pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> {
+ Ok(PythonCodeOracle.object_names(obj))
+ }
+
+ /// Get the idiomatic Python rendering of docstring
+ pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
+ let docstring = textwrap::dedent(docstring);
+ // Escape triple quotes to avoid syntax error
+ let escaped = docstring.replace(r#"""""#, r#"\"\"\""#);
+
+ let wrapped = format!("\"\"\"\n{escaped}\n\"\"\"");
+
+ let spaces = usize::try_from(*spaces).unwrap_or_default();
+ Ok(textwrap::indent(&wrapped, &" ".repeat(spaces)))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_docstring_escape() {
+ let docstring = r#""""This is a docstring beginning with triple quotes.
+Contains "quotes" in it.
+It also has a triple quote: """
+And a even longer quote: """"""#;
+
+ let expected = r#""""
+\"\"\"This is a docstring beginning with triple quotes.
+Contains "quotes" in it.
+It also has a triple quote: \"\"\"
+And a even longer quote: \"\"\"""
+""""#;
+
+ assert_eq!(super::filters::docstring(docstring, &0).unwrap(), expected);
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py
index 82aa534b46..26daa9ba5c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py
@@ -3,13 +3,37 @@ _UNIFFI_RUST_FUTURE_POLL_READY = 0
_UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1
# Stores futures for _uniffi_continuation_callback
-_UniffiContinuationPointerManager = _UniffiPointerManager()
+_UniffiContinuationHandleMap = _UniffiHandleMap()
+
+UNIFFI_GLOBAL_EVENT_LOOP = None
+
+"""
+Set the event loop to use for async functions
+
+This is needed if some async functions run outside of the eventloop, for example:
+ - A non-eventloop thread is spawned, maybe from `EventLoop.run_in_executor` or maybe from the
+ Rust code spawning its own thread.
+ - The Rust code calls an async callback method from a sync callback function, using something
+ like `pollster` to block on the async call.
+
+In this case, we need an event loop to run the Python async function, but there's no eventloop set
+for the thread. Use `uniffi_set_event_loop` to force an eventloop to be used in this case.
+"""
+def uniffi_set_event_loop(eventloop: asyncio.BaseEventLoop):
+ global UNIFFI_GLOBAL_EVENT_LOOP
+ UNIFFI_GLOBAL_EVENT_LOOP = eventloop
+
+def _uniffi_get_event_loop():
+ if UNIFFI_GLOBAL_EVENT_LOOP is not None:
+ return UNIFFI_GLOBAL_EVENT_LOOP
+ else:
+ return asyncio.get_running_loop()
# Continuation callback for async functions
# lift the return value or error and resolve the future, causing the async function to resume.
-@_UNIFFI_FUTURE_CONTINUATION_T
+@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK
def _uniffi_continuation_callback(future_ptr, poll_code):
- (eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr)
+ (eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr)
eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code)
def _uniffi_set_future_result(future, poll_code):
@@ -18,14 +42,15 @@ def _uniffi_set_future_result(future, poll_code):
async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter):
try:
- eventloop = asyncio.get_running_loop()
+ eventloop = _uniffi_get_event_loop()
# Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value
while True:
future = eventloop.create_future()
ffi_poll(
rust_future,
- _UniffiContinuationPointerManager.new_pointer((eventloop, future)),
+ _uniffi_continuation_callback,
+ _UniffiContinuationHandleMap.insert((eventloop, future)),
)
poll_code = await future
if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY:
@@ -37,4 +62,53 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free,
finally:
ffi_free(rust_future)
-_UniffiLib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(_uniffi_continuation_callback)
+{%- if ci.has_async_callback_interface_definition() %}
+def uniffi_trait_interface_call_async(make_call, handle_success, handle_error):
+ async def make_call_and_call_callback():
+ try:
+ handle_success(await make_call())
+ except Exception as e:
+ print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr)
+ traceback.print_exc(file=sys.stderr)
+ handle_error(
+ _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(repr(e)),
+ )
+ eventloop = _uniffi_get_event_loop()
+ task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop)
+ handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task))
+ return UniffiForeignFuture(handle, uniffi_foreign_future_free)
+
+def uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, error_type, lower_error):
+ async def make_call_and_call_callback():
+ try:
+ try:
+ handle_success(await make_call())
+ except error_type as e:
+ handle_error(
+ _UniffiRustCallStatus.CALL_ERROR,
+ lower_error(e),
+ )
+ except Exception as e:
+ print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr)
+ traceback.print_exc(file=sys.stderr)
+ handle_error(
+ _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(repr(e)),
+ )
+ eventloop = _uniffi_get_event_loop()
+ task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop)
+ handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task))
+ return UniffiForeignFuture(handle, uniffi_foreign_future_free)
+
+UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = _UniffiHandleMap()
+
+@UNIFFI_FOREIGN_FUTURE_FREE
+def uniffi_foreign_future_free(handle):
+ (eventloop, task) = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle)
+ eventloop.call_soon(uniffi_foreign_future_do_free, task)
+
+def uniffi_foreign_future_do_free(task):
+ if not task.done():
+ task.cancel()
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
index 6775e9e132..3f8c5d1d4d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
@@ -1,16 +1,20 @@
-class _UniffiConverterBool(_UniffiConverterPrimitive):
+class _UniffiConverterBool:
@classmethod
- def check(cls, value):
+ def check_lower(cls, value):
return not not value
@classmethod
+ def lower(cls, value):
+ return 1 if value else 0
+
+ @staticmethod
+ def lift(value):
+ return value != 0
+
+ @classmethod
def read(cls, buf):
return cls.lift(buf.read_u8())
@classmethod
- def write_unchecked(cls, value, buf):
+ def write(cls, value, buf):
buf.write_u8(value)
-
- @staticmethod
- def lift(value):
- return value != 0
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py
index 196b5b29fa..4d09531322 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py
@@ -7,10 +7,13 @@ class _UniffiConverterBytes(_UniffiConverterRustBuffer):
return buf.read(size)
@staticmethod
- def write(value, buf):
+ def check_lower(value):
try:
memoryview(value)
except TypeError:
raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__))
+
+ @staticmethod
+ def write(value, buf):
buf.write_i32(len(value))
buf.write(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py
new file mode 100644
index 0000000000..676f01177a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py
@@ -0,0 +1,98 @@
+{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %}
+{%- let trait_impl=format!("UniffiTraitImpl{}", name) %}
+
+# Put all the bits inside a class to keep the top-level namespace clean
+class {{ trait_impl }}:
+ # For each method, generate a callback function to pass to Rust
+ {%- for (ffi_callback, meth) in vtable_methods.iter() %}
+
+ @{{ ffi_callback.name()|ffi_callback_name }}
+ def {{ meth.name()|fn_name }}(
+ {%- for arg in ffi_callback.arguments() %}
+ {{ arg.name()|var_name }},
+ {%- endfor -%}
+ {%- if ffi_callback.has_rust_call_status_arg() %}
+ uniffi_call_status_ptr,
+ {%- endif %}
+ ):
+ uniffi_obj = {{ ffi_converter_name }}._handle_map.get(uniffi_handle)
+ def make_call():
+ args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name()|var_name }}), {% endfor %})
+ method = uniffi_obj.{{ meth.name()|fn_name }}
+ return method(*args)
+
+ {% if !meth.is_async() %}
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ def write_return_value(v):
+ uniffi_out_return[0] = {{ return_type|lower_fn }}(v)
+ {%- when None %}
+ write_return_value = lambda v: None
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ _uniffi_trait_interface_call(
+ uniffi_call_status_ptr.contents,
+ make_call,
+ write_return_value,
+ )
+ {%- when Some(error) %}
+ _uniffi_trait_interface_call_with_error(
+ uniffi_call_status_ptr.contents,
+ make_call,
+ write_return_value,
+ {{ error|type_name }},
+ {{ error|lower_fn }},
+ )
+ {%- endmatch %}
+ {%- else %}
+ def handle_success(return_value):
+ uniffi_future_callback(
+ uniffi_callback_data,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ return_type|lower_fn }}(return_value),
+ {%- when None %}
+ {%- endmatch %}
+ _UniffiRustCallStatus.default()
+ )
+ )
+
+ def handle_error(status_code, rust_buffer):
+ uniffi_future_callback(
+ uniffi_callback_data,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ meth.return_type().map(FfiType::from)|ffi_default_value }},
+ {%- when None %}
+ {%- endmatch %}
+ _UniffiRustCallStatus(status_code, rust_buffer),
+ )
+ )
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffi_out_return[0] = uniffi_trait_interface_call_async(make_call, handle_success, handle_error)
+ {%- when Some(error) %}
+ uniffi_out_return[0] = uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, {{ error|type_name }}, {{ error|lower_fn }})
+ {%- endmatch %}
+ {%- endif %}
+ {%- endfor %}
+
+ @{{ "CallbackInterfaceFree"|ffi_callback_name }}
+ def uniffi_free(uniffi_handle):
+ {{ ffi_converter_name }}._handle_map.remove(uniffi_handle)
+
+ # Generate the FFI VTable. This has a field for each callback interface method.
+ uniffi_vtable = {{ vtable|ffi_type_name }}(
+ {%- for (_, meth) in vtable_methods.iter() %}
+ {{ meth.name()|fn_name }},
+ {%- endfor %}
+ uniffi_free
+ )
+ # Send Rust a pointer to the VTable. Note: this means we need to keep the struct alive forever,
+ # or else bad things will happen when Rust tries to access it.
+ _UniffiLib.{{ ffi_init_callback.name() }}(ctypes.byref(uniffi_vtable))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
index 0fe2ab8dc0..d802c90fde 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
@@ -1,42 +1,3 @@
-import threading
-
-class ConcurrentHandleMap:
- """
- A map where inserting, getting and removing data is synchronized with a lock.
- """
-
- def __init__(self):
- # type Handle = int
- self._left_map = {} # type: Dict[Handle, Any]
- self._right_map = {} # type: Dict[Any, Handle]
-
- self._lock = threading.Lock()
- self._current_handle = 0
- self._stride = 1
-
-
- def insert(self, obj):
- with self._lock:
- if obj in self._right_map:
- return self._right_map[obj]
- else:
- handle = self._current_handle
- self._current_handle += self._stride
- self._left_map[handle] = obj
- self._right_map[obj] = handle
- return handle
-
- def get(self, handle):
- with self._lock:
- return self._left_map.get(handle)
-
- def remove(self, handle):
- with self._lock:
- if handle in self._left_map:
- obj = self._left_map.pop(handle)
- del self._right_map[obj]
- return obj
-
# Magic number for the Rust proxy to call using the same mechanism as every other method,
# to free the callback once it's dropped by Rust.
IDX_CALLBACK_FREE = 0
@@ -45,22 +6,12 @@ _UNIFFI_CALLBACK_SUCCESS = 0
_UNIFFI_CALLBACK_ERROR = 1
_UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
-class _UniffiConverterCallbackInterface:
- _handle_map = ConcurrentHandleMap()
-
- def __init__(self, cb):
- self._foreign_callback = cb
-
- def drop(self, handle):
- self.__class__._handle_map.remove(handle)
+class UniffiCallbackInterfaceFfiConverter:
+ _handle_map = _UniffiHandleMap()
@classmethod
def lift(cls, handle):
- obj = cls._handle_map.get(handle)
- if not obj:
- raise InternalError("The object in the handle map has been dropped already")
-
- return obj
+ return cls._handle_map.get(handle)
@classmethod
def read(cls, buf):
@@ -68,6 +19,10 @@ class _UniffiConverterCallbackInterface:
cls.lift(handle)
@classmethod
+ def check_lower(cls, cb):
+ pass
+
+ @classmethod
def lower(cls, cb):
handle = cls._handle_map.insert(cb)
return handle
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
index e0e926757a..a41e58e635 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
@@ -1,105 +1,13 @@
-{%- let cbi = ci|get_callback_interface_definition(id) %}
-{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
+{%- let cbi = ci|get_callback_interface_definition(name) %}
+{%- let ffi_init_callback = cbi.ffi_init_callback() %}
+{%- let protocol_name = type_name.clone() %}
+{%- let protocol_docstring = cbi.docstring() %}
+{%- let vtable = cbi.vtable() %}
+{%- let methods = cbi.methods() %}
+{%- let vtable_methods = cbi.vtable_methods() %}
-{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %}
-
-# Declaration and _UniffiConverters for {{ type_name }} Callback Interface
-
-class {{ type_name }}:
- {% for meth in cbi.methods() -%}
- def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
- raise NotImplementedError
-
- {% endfor %}
-
-def py_{{ foreign_callback }}(handle, method, args_data, args_len, buf_ptr):
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
- def {{ method_name }}(python_callback, args_stream, buf_ptr):
- {#- Unpacking args from the _UniffiRustBuffer #}
- def makeCall():
- {#- Calling the concrete callback object #}
- {%- if meth.arguments().len() != 0 -%}
- return python_callback.{{ meth.name()|fn_name }}(
- {% for arg in meth.arguments() -%}
- {{ arg|read_fn }}(args_stream)
- {%- if !loop.last %}, {% endif %}
- {% endfor -%}
- )
- {%- else %}
- return python_callback.{{ meth.name()|fn_name }}()
- {%- endif %}
-
- def makeCallAndHandleReturn():
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- rval = makeCall()
- with _UniffiRustBuffer.alloc_with_builder() as builder:
- {{ return_type|write_fn }}(rval, builder)
- buf_ptr[0] = builder.finalize()
- {%- when None %}
- makeCall()
- {%- endmatch %}
- return _UNIFFI_CALLBACK_SUCCESS
-
- {%- match meth.throws_type() %}
- {%- when None %}
- return makeCallAndHandleReturn()
- {%- when Some(err) %}
- try:
- return makeCallAndHandleReturn()
- except {{ err|type_name }} as e:
- # Catch errors declared in the UDL file
- with _UniffiRustBuffer.alloc_with_builder() as builder:
- {{ err|write_fn }}(e, builder)
- buf_ptr[0] = builder.finalize()
- return _UNIFFI_CALLBACK_ERROR
- {%- endmatch %}
-
- {% endfor %}
-
- cb = {{ ffi_converter_name }}.lift(handle)
- if not cb:
- raise InternalError("No callback in handlemap; this is a uniffi bug")
-
- if method == IDX_CALLBACK_FREE:
- {{ ffi_converter_name }}.drop(handle)
- # Successfull return
- # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- return _UNIFFI_CALLBACK_SUCCESS
-
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
- if method == {{ loop.index }}:
- # Call the method and handle any errors
- # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for details
- try:
- return {{ method_name }}(cb, _UniffiRustBufferStream(args_data, args_len), buf_ptr)
- except BaseException as e:
- # Catch unexpected errors
- try:
- # Try to serialize the exception into a String
- buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e))
- except:
- # If that fails, just give up
- pass
- return _UNIFFI_CALLBACK_UNEXPECTED_ERROR
- {% endfor %}
-
- # This should never happen, because an out of bounds method index won't
- # ever be used. Once we can catch errors, we should return an InternalException.
- # https://github.com/mozilla/uniffi-rs/issues/351
-
- # An unexpected error happened.
- # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- return _UNIFFI_CALLBACK_UNEXPECTED_ERROR
-
-# We need to keep this function reference alive:
-# if they get GC'd while in use then UniFFI internals could attempt to call a function
-# that is in freed memory.
-# That would be...uh...bad. Yeah, that's the word. Bad.
-{{ foreign_callback }} = _UNIFFI_FOREIGN_CALLBACK_T(py_{{ foreign_callback }})
-_rust_call(lambda err: _UniffiLib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err))
+{% include "Protocol.py" %}
+{% include "CallbackInterfaceImpl.py" %}
# The _UniffiConverter which transforms the Callbacks in to Handles to pass to Rust.
-{{ ffi_converter_name }} = _UniffiConverterCallbackInterface({{ foreign_callback }})
+{{ ffi_converter_name }} = UniffiCallbackInterfaceFfiConverter()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
index 5be6155b84..f75a85dc27 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
@@ -18,6 +18,10 @@ class _UniffiConverterType{{ name }}:
return {{ builtin|ffi_converter_name }}.lift(value)
@staticmethod
+ def check_lower(value):
+ return {{ builtin|ffi_converter_name }}.check_lower(value)
+
+ @staticmethod
def lower(value):
return {{ builtin|ffi_converter_name }}.lower(value)
@@ -52,6 +56,11 @@ class _UniffiConverterType{{ name }}:
return {{ config.into_custom.render("builtin_value") }}
@staticmethod
+ def check_lower(value):
+ builtin_value = {{ config.from_custom.render("value") }}
+ return {{ builtin|check_lower_fn }}(builtin_value)
+
+ @staticmethod
def lower(value):
builtin_value = {{ config.from_custom.render("value") }}
return {{ builtin|lower_fn }}(builtin_value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
index 10974e009d..ecb035b7f4 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
@@ -12,10 +12,14 @@ class _UniffiConverterDuration(_UniffiConverterRustBuffer):
return datetime.timedelta(seconds=seconds, microseconds=microseconds)
@staticmethod
- def write(value, buf):
+ def check_lower(value):
seconds = value.seconds + value.days * 24 * 3600
- nanoseconds = value.microseconds * 1000
if seconds < 0:
raise ValueError("Invalid duration, must be non-negative")
+
+ @staticmethod
+ def write(value, buf):
+ seconds = value.seconds + value.days * 24 * 3600
+ nanoseconds = value.microseconds * 1000
buf.write_i64(seconds)
buf.write_u32(nanoseconds)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
index 84d089baf9..d07dd1c44a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
@@ -7,31 +7,59 @@
{% if e.is_flat() %}
class {{ type_name }}(enum.Enum):
- {% for variant in e.variants() -%}
- {{ variant.name()|enum_variant_py }} = {{ loop.index }}
+ {%- call py::docstring(e, 4) %}
+ {%- for variant in e.variants() %}
+ {{ variant.name()|enum_variant_py }} = {{ e|variant_discr_literal(loop.index0) }}
+ {%- call py::docstring(variant, 4) %}
{% endfor %}
{% else %}
class {{ type_name }}:
+ {%- call py::docstring(e, 4) %}
def __init__(self):
raise RuntimeError("{{ type_name }} cannot be instantiated directly")
# Each enum variant is a nested class of the enum itself.
{% for variant in e.variants() -%}
class {{ variant.name()|enum_variant_py }}:
- {% for field in variant.fields() %}
- {{- field.name()|var_name }}: "{{- field|type_name }}";
+ {%- call py::docstring(variant, 8) %}
+
+ {%- if variant.has_nameless_fields() %}
+ def __init__(self, *values):
+ if len(values) != {{ variant.fields().len() }}:
+ raise TypeError(f"Expected a tuple of len {{ variant.fields().len() }}, found len {len(values)}")
+ {%- for field in variant.fields() %}
+ if not isinstance(values[{{ loop.index0 }}], {{ field|type_name }}):
+ raise TypeError(f"unexpected type for tuple element {{ loop.index0 }} - expected '{{ field|type_name }}', got '{type(values[{{ loop.index0 }}])}'")
+ {%- endfor %}
+ self._values = values
+
+ def __getitem__(self, index):
+ return self._values[index]
+
+ def __str__(self):
+ return f"{{ type_name }}.{{ variant.name()|enum_variant_py }}{self._values!r}"
+
+ def __eq__(self, other):
+ if not other.is_{{ variant.name()|var_name }}():
+ return False
+ return self._values == other._values
+
+ {%- else -%}
+ {%- for field in variant.fields() %}
+ {{ field.name()|var_name }}: "{{ field|type_name }}"
+ {%- call py::docstring(field, 8) %}
{%- endfor %}
@typing.no_type_check
def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}):
- {% if variant.has_fields() %}
+ {%- if variant.has_fields() %}
{%- for field in variant.fields() %}
self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
{%- endfor %}
- {% else %}
+ {%- else %}
pass
- {% endif %}
+ {%- endif %}
def __str__(self):
return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
@@ -44,6 +72,7 @@ class {{ type_name }}:
return False
{%- endfor %}
return True
+ {% endif %}
{% endfor %}
# For each variant, we have an `is_NAME` method for easily checking
@@ -81,6 +110,30 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
{%- endfor %}
raise InternalError("Raw enum value doesn't match any cases")
+ @staticmethod
+ def check_lower(value):
+ {%- if e.variants().is_empty() %}
+ pass
+ {%- else %}
+ {%- for variant in e.variants() %}
+ {%- if e.is_flat() %}
+ if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
+ {%- else %}
+ if value.is_{{ variant.name()|var_name }}():
+ {%- endif %}
+ {%- for field in variant.fields() %}
+ {%- if variant.has_nameless_fields() %}
+ {{ field|check_lower_fn }}(value._values[{{ loop.index0 }}])
+ {%- else %}
+ {{ field|check_lower_fn }}(value.{{ field.name()|var_name }})
+ {%- endif %}
+ {%- endfor %}
+ return
+ {%- endfor %}
+ raise ValueError(value)
+ {%- endif %}
+
+ @staticmethod
def write(value, buf):
{%- for variant in e.variants() %}
{%- if e.is_flat() %}
@@ -90,7 +143,11 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
if value.is_{{ variant.name()|var_name }}():
buf.write_i32({{ loop.index }})
{%- for field in variant.fields() %}
+ {%- if variant.has_nameless_fields() %}
+ {{ field|write_fn }}(value._values[{{ loop.index0 }}], buf)
+ {%- else %}
{{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
index 26a1e6452a..0911ff559a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
@@ -5,6 +5,7 @@
# __dict__. All of this happens in dummy class to avoid polluting the module
# namespace.
class {{ type_name }}(Exception):
+ {%- call py::docstring(e, 4) %}
pass
_UniffiTemp{{ type_name }} = {{ type_name }}
@@ -14,10 +15,14 @@ class {{ type_name }}: # type: ignore
{%- let variant_type_name = variant.name()|class_name -%}
{%- if e.is_flat() %}
class {{ variant_type_name }}(_UniffiTemp{{ type_name }}):
+ {%- call py::docstring(variant, 8) %}
+
def __repr__(self):
return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(str(self)))
{%- else %}
class {{ variant_type_name }}(_UniffiTemp{{ type_name }}):
+ {%- call py::docstring(variant, 8) %}
+
def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}):
{%- if variant.has_fields() %}
super().__init__(", ".join([
@@ -60,6 +65,20 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
raise InternalError("Raw enum value doesn't match any cases")
@staticmethod
+ def check_lower(value):
+ {%- if e.variants().is_empty() %}
+ pass
+ {%- else %}
+ {%- for variant in e.variants() %}
+ if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}):
+ {%- for field in variant.fields() %}
+ {{ field|check_lower_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ return
+ {%- endfor %}
+ {%- endif %}
+
+ @staticmethod
def write(value, buf):
{%- for variant in e.variants() %}
if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}):
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
index 71e05e8b06..6c0cee85ef 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
@@ -1,9 +1,9 @@
-{%- let ns = namespace|fn_name %}
+{%- let module = python_config.module_for_namespace(namespace) -%}
# External type {{name}} is in namespace "{{namespace}}", crate {{module_path}}
{%- let ffi_converter_name = "_UniffiConverterType{}"|format(name) %}
-{{ self.add_import_of(ns, ffi_converter_name) }}
-{{ self.add_import_of(ns, name) }} {#- import the type alias itself -#}
+{{ self.add_import_of(module, ffi_converter_name) }}
+{{ self.add_import_of(module, name|class_name) }} {#- import the type alias itself -#}
{%- let rustbuffer_local_name = "_UniffiRustBuffer{}"|format(name) %}
-{{ self.add_import_of_as(ns, "_UniffiRustBuffer", rustbuffer_local_name) }}
+{{ self.add_import_of_as(module, "_UniffiRustBuffer", rustbuffer_local_name) }}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
index a52107a638..49a1a7286e 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
@@ -4,5 +4,5 @@ class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat):
return buf.read_float()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_float(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
index 772f5080e9..e2084c7b13 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
@@ -4,5 +4,5 @@ class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat):
return buf.read_double()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_double(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py
deleted file mode 100644
index 6a6932fed0..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# FFI code for the ForeignExecutor type
-
-{{ self.add_import("asyncio") }}
-
-_UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0
-_UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1
-_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0
-_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED = 1
-_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2
-
-class {{ ffi_converter_name }}:
- _pointer_manager = _UniffiPointerManager()
-
- @classmethod
- def lower(cls, eventloop):
- if not isinstance(eventloop, asyncio.BaseEventLoop):
- raise TypeError("_uniffi_executor_callback: Expected EventLoop instance")
- return cls._pointer_manager.new_pointer(eventloop)
-
- @classmethod
- def write(cls, eventloop, buf):
- buf.write_c_size_t(cls.lower(eventloop))
-
- @classmethod
- def read(cls, buf):
- return cls.lift(buf.read_c_size_t())
-
- @classmethod
- def lift(cls, value):
- return cls._pointer_manager.lookup(value)
-
-@_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T
-def _uniffi_executor_callback(eventloop_address, delay, task_ptr, task_data):
- if task_ptr is None:
- {{ ffi_converter_name }}._pointer_manager.release_pointer(eventloop_address)
- return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- else:
- eventloop = {{ ffi_converter_name }}._pointer_manager.lookup(eventloop_address)
- if eventloop.is_closed():
- return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED
-
- callback = _UNIFFI_RUST_TASK(task_ptr)
- # FIXME: there's no easy way to get a callback when an eventloop is closed. This means that
- # if eventloop is called before the `call_soon_threadsafe()` calls are invoked, the call
- # will never happen and we will probably leak a resource.
- if delay == 0:
- # This can be called from any thread, so make sure to use `call_soon_threadsafe'
- eventloop.call_soon_threadsafe(callback, task_data,
- _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS)
- else:
- # For delayed tasks, we use `call_soon_threadsafe()` + `call_later()` to make the
- # operation threadsafe
- eventloop.call_soon_threadsafe(eventloop.call_later, delay / 1000.0, callback,
- task_data, _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS)
- return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
-
-# Register the callback with the scaffolding
-{%- match ci.ffi_foreign_executor_callback_set() %}
-{%- when Some with (fn) %}
-_UniffiLib.{{ fn.name() }}(_uniffi_executor_callback)
-{%- when None %}
-{#- No foreign executor, we don't set anything #}
-{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py
new file mode 100644
index 0000000000..f7c13cf745
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py
@@ -0,0 +1,33 @@
+class _UniffiHandleMap:
+ """
+ A map where inserting, getting and removing data is synchronized with a lock.
+ """
+
+ def __init__(self):
+ # type Handle = int
+ self._map = {} # type: Dict[Handle, Any]
+ self._lock = threading.Lock()
+ self._counter = itertools.count()
+
+ def insert(self, obj):
+ with self._lock:
+ handle = next(self._counter)
+ self._map[handle] = obj
+ return handle
+
+ def get(self, handle):
+ try:
+ with self._lock:
+ return self._map[handle]
+ except KeyError:
+ raise InternalError("UniffiHandleMap.get: Invalid handle")
+
+ def remove(self, handle):
+ try:
+ with self._lock:
+ return self._map.pop(handle)
+ except KeyError:
+ raise InternalError("UniffiHandleMap.remove: Invalid handle")
+
+ def __len__(self):
+ return len(self._map)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
index dca962f176..5d4bcbba89 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
@@ -16,15 +16,19 @@ class _UniffiRustCallStatus(ctypes.Structure):
# These match the values from the uniffi::rustcalls module
CALL_SUCCESS = 0
CALL_ERROR = 1
- CALL_PANIC = 2
+ CALL_UNEXPECTED_ERROR = 2
+
+ @staticmethod
+ def default():
+ return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default())
def __str__(self):
if self.code == _UniffiRustCallStatus.CALL_SUCCESS:
return "_UniffiRustCallStatus(CALL_SUCCESS)"
elif self.code == _UniffiRustCallStatus.CALL_ERROR:
return "_UniffiRustCallStatus(CALL_ERROR)"
- elif self.code == _UniffiRustCallStatus.CALL_PANIC:
- return "_UniffiRustCallStatus(CALL_PANIC)"
+ elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR:
+ return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)"
else:
return "_UniffiRustCallStatus(<invalid code>)"
@@ -37,7 +41,7 @@ def _rust_call_with_error(error_ffi_converter, fn, *args):
#
# This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code.
# error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result.
- call_status = _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer(0, 0, None))
+ call_status = _UniffiRustCallStatus.default()
args_with_error = args + (ctypes.byref(call_status),)
result = fn(*args_with_error)
@@ -53,7 +57,7 @@ def _uniffi_check_call_status(error_ffi_converter, call_status):
raise InternalError("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None")
else:
raise error_ffi_converter.lift(call_status.error_buf)
- elif call_status.code == _UniffiRustCallStatus.CALL_PANIC:
+ elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR:
# When the rust code sees a panic, it tries to construct a _UniffiRustBuffer
# with the message. But if that code panics, then it just sends back
# an empty buffer.
@@ -66,10 +70,20 @@ def _uniffi_check_call_status(error_ffi_converter, call_status):
raise InternalError("Invalid _UniffiRustCallStatus code: {}".format(
call_status.code))
-# A function pointer for a callback as defined by UniFFI.
-# Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int`
-_UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer))
-
-# UniFFI future continuation
-_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8)
+def _uniffi_trait_interface_call(call_status, make_call, write_return_value):
+ try:
+ return write_return_value(make_call())
+ except Exception as e:
+ call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR
+ call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e))
+def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error):
+ try:
+ try:
+ return write_return_value(make_call())
+ except error_type as e:
+ call_status.code = _UniffiRustCallStatus.CALL_ERROR
+ call_status.error_buf = lower_error(e)
+ except Exception as e:
+ call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR
+ call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
index 99f19dc1c0..befa563384 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterInt16(_UniffiConverterPrimitiveInt):
return buf.read_i16()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_i16(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
index 3b142c8749..70644f6717 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterInt32(_UniffiConverterPrimitiveInt):
return buf.read_i32()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_i32(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
index 6e94379cbf..232f127bd6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterInt64(_UniffiConverterPrimitiveInt):
return buf.read_i64()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_i64(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
index 732530e3cb..c1de1625e7 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterInt8(_UniffiConverterPrimitiveInt):
return buf.read_i8()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_i8(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
index 387227ed09..a09ca28a30 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
@@ -3,6 +3,12 @@
class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
@classmethod
+ def check_lower(cls, items):
+ for (key, value) in items.items():
+ {{ key_ffi_converter }}.check_lower(key)
+ {{ value_ffi_converter }}.check_lower(value)
+
+ @classmethod
def write(cls, items, buf):
buf.write_i32(len(items))
for (key, value) in items.items():
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
index fac6cd5564..1929f9aad6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
@@ -1,22 +1,6 @@
# Define some ctypes FFI types that we use in the library
"""
-ctypes type for the foreign executor callback. This is a built-in interface for scheduling
-tasks
-
-Args:
- executor: opaque c_size_t value representing the eventloop
- delay: delay in ms
- task: function pointer to the task callback
- task_data: void pointer to the task callback data
-
-Normally we should call task(task_data) after the detail.
-However, when task is NULL this indicates that Rust has dropped the ForeignExecutor and we should
-decrease the EventLoop refcount.
-"""
-_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int8, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p)
-
-"""
Function pointer for a Rust task, which a callback function that takes a opaque pointer
"""
_UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8)
@@ -25,7 +9,7 @@ def _uniffi_future_callback_t(return_type):
"""
Factory function to create callback function types for async functions
"""
- return ctypes.CFUNCTYPE(None, ctypes.c_size_t, return_type, _UniffiRustCallStatus)
+ return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus)
def _uniffi_load_indirect():
"""
@@ -72,12 +56,37 @@ def _uniffi_check_api_checksums(lib):
# This is an implementation detail which will be called internally by the public API.
_UniffiLib = _uniffi_load_indirect()
-{%- for func in ci.iter_ffi_function_definitions() %}
+
+{%- for def in ci.ffi_definitions() %}
+{%- match def %}
+{%- when FfiDefinition::CallbackFunction(callback) %}
+{{ callback.name()|ffi_callback_name }} = ctypes.CFUNCTYPE(
+ {%- match callback.return_type() %}
+ {%- when Some(return_type) %}{{ return_type|ffi_type_name }},
+ {%- when None %}None,
+ {%- endmatch %}
+ {%- for arg in callback.arguments() -%}
+ {{ arg.type_().borrow()|ffi_type_name }},
+ {%- endfor -%}
+ {%- if callback.has_rust_call_status_arg() %}
+ ctypes.POINTER(_UniffiRustCallStatus),
+ {%- endif %}
+)
+{%- when FfiDefinition::Struct(ffi_struct) %}
+class {{ ffi_struct.name()|ffi_struct_name }}(ctypes.Structure):
+ _fields_ = [
+ {%- for field in ffi_struct.fields() %}
+ ("{{ field.name()|var_name }}", {{ field.type_().borrow()|ffi_type_name }}),
+ {%- endfor %}
+ ]
+{%- when FfiDefinition::Function(func) %}
_UniffiLib.{{ func.name() }}.argtypes = (
{%- call py::arg_list_ffi_decl(func) -%}
)
_UniffiLib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %}
+{%- endmatch %}
{%- endfor %}
+
{# Ensure to call the contract verification only after we defined all functions. -#}
_uniffi_check_contract_api_version(_UniffiLib)
_uniffi_check_api_checksums(_UniffiLib)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
index 7e98f7c46f..18dca4743a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
@@ -1,14 +1,33 @@
{%- let obj = ci|get_object_definition(name) %}
-
-class {{ type_name }}:
+{%- let (protocol_name, impl_name) = obj|object_names %}
+{%- let methods = obj.methods() %}
+{%- let protocol_docstring = obj.docstring() %}
+
+{% include "Protocol.py" %}
+
+{% if ci.is_name_used_as_error(name) %}
+class {{ impl_name }}(Exception):
+{%- else %}
+class {{ impl_name }}:
+{%- endif %}
+ {%- call py::docstring(obj, 4) %}
_pointer: ctypes.c_void_p
{%- match obj.primary_constructor() %}
{%- when Some with (cons) %}
+{%- if cons.is_async() %}
+ def __init__(self, *args, **kw):
+ raise ValueError("async constructors not supported.")
+{%- else %}
def __init__(self, {% call py::arg_list_decl(cons) -%}):
+ {%- call py::docstring(cons, 8) %}
{%- call py::setup_args_extra_indent(cons) %}
self._pointer = {% call py::to_ffi_call(cons) %}
+{%- endif %}
{%- when None %}
+ {# no __init__ means simple construction without a pointer works, which can confuse #}
+ def __init__(self, *args, **kwargs):
+ raise ValueError("This class has no default constructor")
{%- endmatch %}
def __del__(self):
@@ -17,6 +36,9 @@ class {{ type_name }}:
if pointer is not None:
_rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer)
+ def _uniffi_clone_pointer(self):
+ return _rust_call(_UniffiLib.{{ obj.ffi_object_clone().name() }}, self._pointer)
+
# Used by alternative constructors or any methods which return this type.
@classmethod
def _make_instance_(cls, pointer):
@@ -29,17 +51,32 @@ class {{ type_name }}:
{%- for cons in obj.alternate_constructors() %}
@classmethod
+{%- if cons.is_async() %}
+ async def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
+ {%- call py::docstring(cons, 8) %}
+ {%- call py::setup_args_extra_indent(cons) %}
+
+ return await _uniffi_rust_call_async(
+ _UniffiLib.{{ cons.ffi_func().name() }}({% call py::arg_list_lowered(cons) %}),
+ _UniffiLib.{{ cons.ffi_rust_future_poll(ci) }},
+ _UniffiLib.{{ cons.ffi_rust_future_complete(ci) }},
+ _UniffiLib.{{ cons.ffi_rust_future_free(ci) }},
+ {{ ffi_converter_name }}.lift,
+ {% call py::error_ffi_converter(cons) %}
+ )
+{%- else %}
def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
+ {%- call py::docstring(cons, 8) %}
{%- call py::setup_args_extra_indent(cons) %}
# Call the (fallible) function before creating any half-baked object instances.
pointer = {% call py::to_ffi_call(cons) %}
return cls._make_instance_(pointer)
+{%- endif %}
{% endfor %}
{%- for meth in obj.methods() -%}
{%- call py::method_decl(meth.name()|fn_name, meth) %}
-{% endfor %}
-
+{%- endfor %}
{%- for tm in obj.uniffi_traits() -%}
{%- match tm %}
{%- when UniffiTrait::Debug { fmt } %}
@@ -51,37 +88,84 @@ class {{ type_name }}:
if not isinstance(other, {{ type_name }}):
return NotImplemented
- return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %})
+ return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", eq) %})
def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}:
if not isinstance(other, {{ type_name }}):
return NotImplemented
- return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %})
+ return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", ne) %})
{%- when UniffiTrait::Hash { hash } %}
{%- call py::method_decl("__hash__", hash) %}
-{% endmatch %}
-{% endfor %}
-
-
-class {{ ffi_converter_name }}:
+{%- endmatch %}
+{%- endfor %}
+
+{%- if obj.has_callback_interface() %}
+{%- let ffi_init_callback = obj.ffi_init_callback() %}
+{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %}
+{%- let vtable_methods = obj.vtable_methods() %}
+{% include "CallbackInterfaceImpl.py" %}
+{%- endif %}
+
+{# Objects as error #}
+{%- if ci.is_name_used_as_error(name) %}
+{# Due to some mismatches in the ffi converter mechanisms, errors are forced to be a RustBuffer #}
+class {{ ffi_converter_name }}__as_error(_UniffiConverterRustBuffer):
@classmethod
def read(cls, buf):
- ptr = buf.read_u64()
- if ptr == 0:
- raise InternalError("Raw pointer value was null")
- return cls.lift(ptr)
+ raise NotImplementedError()
@classmethod
def write(cls, value, buf):
- if not isinstance(value, {{ type_name }}):
- raise TypeError("Expected {{ type_name }} instance, {} found".format(type(value).__name__))
- buf.write_u64(cls.lower(value))
+ raise NotImplementedError()
@staticmethod
def lift(value):
- return {{ type_name }}._make_instance_(value)
+ # Errors are always a rust buffer holding a pointer - which is a "read"
+ with value.consume_with_stream() as stream:
+ return {{ ffi_converter_name }}.read(stream)
@staticmethod
def lower(value):
- return value._pointer
+ raise NotImplementedError()
+
+{%- endif %}
+
+class {{ ffi_converter_name }}:
+ {%- if obj.has_callback_interface() %}
+ _handle_map = _UniffiHandleMap()
+ {%- endif %}
+
+ @staticmethod
+ def lift(value: int):
+ return {{ impl_name }}._make_instance_(value)
+
+ @staticmethod
+ def check_lower(value: {{ type_name }}):
+ {%- if obj.has_callback_interface() %}
+ pass
+ {%- else %}
+ if not isinstance(value, {{ impl_name }}):
+ raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__))
+ {%- endif %}
+
+ @staticmethod
+ def lower(value: {{ protocol_name }}):
+ {%- if obj.has_callback_interface() %}
+ return {{ ffi_converter_name }}._handle_map.insert(value)
+ {%- else %}
+ if not isinstance(value, {{ impl_name }}):
+ raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__))
+ return value._uniffi_clone_pointer()
+ {%- endif %}
+
+ @classmethod
+ def read(cls, buf: _UniffiRustBuffer):
+ ptr = buf.read_u64()
+ if ptr == 0:
+ raise InternalError("Raw pointer value was null")
+ return cls.lift(ptr)
+
+ @classmethod
+ def write(cls, value: {{ protocol_name }}, buf: _UniffiRustBuffer):
+ buf.write_u64(cls.lower(value))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
index e406c51d49..4c07ae3e34 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
@@ -2,6 +2,11 @@
class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
@classmethod
+ def check_lower(cls, value):
+ if value is not None:
+ {{ inner_ffi_converter }}.check_lower(value)
+
+ @classmethod
def write(cls, value, buf):
if value is None:
buf.write_u8(0)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py
deleted file mode 100644
index 23aa28eab4..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py
+++ /dev/null
@@ -1,68 +0,0 @@
-class _UniffiPointerManagerCPython:
- """
- Manage giving out pointers to Python objects on CPython
-
- This class is used to generate opaque pointers that reference Python objects to pass to Rust.
- It assumes a CPython platform. See _UniffiPointerManagerGeneral for the alternative.
- """
-
- def new_pointer(self, obj):
- """
- Get a pointer for an object as a ctypes.c_size_t instance
-
- Each call to new_pointer() must be balanced with exactly one call to release_pointer()
-
- This returns a ctypes.c_size_t. This is always the same size as a pointer and can be
- interchanged with pointers for FFI function arguments and return values.
- """
- # IncRef the object since we're going to pass a pointer to Rust
- ctypes.pythonapi.Py_IncRef(ctypes.py_object(obj))
- # id() is the object address on CPython
- # (https://docs.python.org/3/library/functions.html#id)
- return id(obj)
-
- def release_pointer(self, address):
- py_obj = ctypes.cast(address, ctypes.py_object)
- obj = py_obj.value
- ctypes.pythonapi.Py_DecRef(py_obj)
- return obj
-
- def lookup(self, address):
- return ctypes.cast(address, ctypes.py_object).value
-
-class _UniffiPointerManagerGeneral:
- """
- Manage giving out pointers to Python objects on non-CPython platforms
-
- This has the same API as _UniffiPointerManagerCPython, but doesn't assume we're running on
- CPython and is slightly slower.
-
- Instead of using real pointers, it maps integer values to objects and returns the keys as
- c_size_t values.
- """
-
- def __init__(self):
- self._map = {}
- self._lock = threading.Lock()
- self._current_handle = 0
-
- def new_pointer(self, obj):
- with self._lock:
- handle = self._current_handle
- self._current_handle += 1
- self._map[handle] = obj
- return handle
-
- def release_pointer(self, handle):
- with self._lock:
- return self._map.pop(handle)
-
- def lookup(self, handle):
- with self._lock:
- return self._map[handle]
-
-# Pick an pointer manager implementation based on the platform
-if platform.python_implementation() == 'CPython':
- _UniffiPointerManager = _UniffiPointerManagerCPython # type: ignore
-else:
- _UniffiPointerManager = _UniffiPointerManagerGeneral # type: ignore
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py
new file mode 100644
index 0000000000..3b7e93596a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py
@@ -0,0 +1,9 @@
+class {{ protocol_name }}(typing.Protocol):
+ {%- call py::docstring_value(protocol_docstring, 4) %}
+ {%- for meth in methods.iter() %}
+ def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
+ {%- call py::docstring(meth, 8) %}
+ raise NotImplementedError
+ {%- else %}
+ pass
+ {%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
index 99a30e120f..0b5634eb52 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
@@ -1,11 +1,14 @@
{%- let rec = ci|get_record_definition(name) %}
class {{ type_name }}:
- {% for field in rec.fields() %}
- {{- field.name()|var_name }}: "{{- field|type_name }}";
+ {%- call py::docstring(rec, 4) %}
+ {%- for field in rec.fields() %}
+ {{ field.name()|var_name }}: "{{ field|type_name }}"
+ {%- call py::docstring(field, 4) %}
{%- endfor %}
+ {%- if rec.has_fields() %}
@typing.no_type_check
- def __init__(self, {% for field in rec.fields() %}
+ def __init__(self, *, {% for field in rec.fields() %}
{{- field.name()|var_name }}: "{{- field|type_name }}"
{%- if field.default_value().is_some() %} = _DEFAULT{% endif %}
{%- if !loop.last %}, {% endif %}
@@ -22,6 +25,7 @@ class {{ type_name }}:
self.{{ field_name }} = {{ field_name }}
{%- endmatch %}
{%- endfor %}
+ {%- endif %}
def __str__(self):
return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
@@ -43,7 +47,21 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
)
@staticmethod
+ def check_lower(value):
+ {%- if rec.fields().is_empty() %}
+ pass
+ {%- else %}
+ {%- for field in rec.fields() %}
+ {{ field|check_lower_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ {%- endif %}
+
+ @staticmethod
def write(value, buf):
+ {%- if rec.has_fields() %}
{%- for field in rec.fields() %}
{{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
{%- endfor %}
+ {%- else %}
+ pass
+ {%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
index daabd5b4b9..4db74fb157 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
@@ -1,28 +1,16 @@
# Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI.
class _UniffiConverterPrimitive:
@classmethod
- def check(cls, value):
- return value
-
- @classmethod
def lift(cls, value):
return value
@classmethod
def lower(cls, value):
- return cls.lowerUnchecked(cls.check(value))
-
- @classmethod
- def lowerUnchecked(cls, value):
return value
- @classmethod
- def write(cls, value, buf):
- cls.write_unchecked(cls.check(value), buf)
-
class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive):
@classmethod
- def check(cls, value):
+ def check_lower(cls, value):
try:
value = value.__index__()
except Exception:
@@ -31,18 +19,16 @@ class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive):
raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__))
if not cls.VALUE_MIN <= value < cls.VALUE_MAX:
raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX))
- return super().check(value)
class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive):
@classmethod
- def check(cls, value):
+ def check_lower(cls, value):
try:
value = value.__float__()
except Exception:
raise TypeError("must be real number, not {}".format(type(value).__name__))
if not isinstance(value, float):
raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__))
- return super().check(value)
# Helper class for wrapper types that will always go through a _UniffiRustBuffer.
# Classes should inherit from this and implement the `read` and `write` static methods.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
index c317a632fc..44e0ba1001 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
@@ -1,12 +1,16 @@
class _UniffiRustBuffer(ctypes.Structure):
_fields_ = [
- ("capacity", ctypes.c_int32),
- ("len", ctypes.c_int32),
+ ("capacity", ctypes.c_uint64),
+ ("len", ctypes.c_uint64),
("data", ctypes.POINTER(ctypes.c_char)),
]
@staticmethod
+ def default():
+ return _UniffiRustBuffer(0, 0, None)
+
+ @staticmethod
def alloc(size):
return _rust_call(_UniffiLib.{{ ci.ffi_rustbuffer_alloc().name() }}, size)
@@ -137,9 +141,6 @@ class _UniffiRustBufferStream:
def read_double(self):
return self._unpack_from(8, ">d")
- def read_c_size_t(self):
- return self._unpack_from(ctypes.sizeof(ctypes.c_size_t) , "@N")
-
class _UniffiRustBufferBuilder:
"""
Helper for structured writing of bytes into a _UniffiRustBuffer.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
index 3c9f5a4596..3c30d9f9f5 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
@@ -2,6 +2,11 @@
class {{ ffi_converter_name}}(_UniffiConverterRustBuffer):
@classmethod
+ def check_lower(cls, value):
+ for item in value:
+ {{ inner_ffi_converter }}.check_lower(item)
+
+ @classmethod
def write(cls, value, buf):
items = len(value)
buf.write_i32(items)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
index 40890b6abc..d574edd09f 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
@@ -1,6 +1,6 @@
class _UniffiConverterString:
@staticmethod
- def check(value):
+ def check_lower(value):
if not isinstance(value, str):
raise TypeError("argument must be str, not {}".format(type(value).__name__))
return value
@@ -15,7 +15,6 @@ class _UniffiConverterString:
@staticmethod
def write(value, buf):
- value = _UniffiConverterString.check(value)
utf8_bytes = value.encode("utf-8")
buf.write_i32(len(utf8_bytes))
buf.write(utf8_bytes)
@@ -27,7 +26,6 @@ class _UniffiConverterString:
@staticmethod
def lower(value):
- value = _UniffiConverterString.check(value)
with _UniffiRustBuffer.alloc_with_builder() as builder:
builder.write(value.encode("utf-8"))
return builder.finalize()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
index 8402f6095d..76d7f8bcdb 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
@@ -18,6 +18,10 @@ class _UniffiConverterTimestamp(_UniffiConverterRustBuffer):
return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds)
@staticmethod
+ def check_lower(value):
+ pass
+
+ @staticmethod
def write(value, buf):
if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc):
sign = 1
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
index f258b60a1c..230b9e853f 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
@@ -1,7 +1,15 @@
{%- if func.is_async() %}
-def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
- return _uniffi_rust_call_async(
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}":
+{% when None %}
+async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None:
+{% endmatch %}
+
+ {%- call py::docstring(func, 4) %}
+ {%- call py::setup_args(func) %}
+ return await _uniffi_rust_call_async(
_UniffiLib.{{ func.ffi_func().name() }}({% call py::arg_list_lowered(func) %}),
_UniffiLib.{{func.ffi_rust_future_poll(ci) }},
_UniffiLib.{{func.ffi_rust_future_complete(ci) }},
@@ -13,13 +21,7 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
{%- when None %}
lambda val: None,
{% endmatch %}
- # Error FFI converter
- {%- match func.throws_type() %}
- {%- when Some(e) %}
- {{ e|ffi_converter_name }},
- {%- when None %}
- None,
- {%- endmatch %}
+ {% call py::error_ffi_converter(func) %}
)
{%- else %}
@@ -27,11 +29,13 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
{%- when Some with (return_type) %}
def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}":
+ {%- call py::docstring(func, 4) %}
{%- call py::setup_args(func) %}
return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %})
{% when None %}
-def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
+def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None:
+ {%- call py::docstring(func, 4) %}
{%- call py::setup_args(func) %}
{% call py::to_ffi_call(func) %}
{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
index 5e05314c37..4aaed253e0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
@@ -85,7 +85,7 @@
{%- when Type::Map { key_type, value_type } %}
{%- include "MapTemplate.py" %}
-{%- when Type::CallbackInterface { name: id, module_path } %}
+{%- when Type::CallbackInterface { name, module_path } %}
{%- include "CallbackInterfaceTemplate.py" %}
{%- when Type::Custom { name, module_path, builtin } %}
@@ -94,9 +94,6 @@
{%- when Type::External { name, module_path, namespace, kind, tagged } %}
{%- include "ExternalTemplate.py" %}
-{%- when Type::ForeignExecutor %}
-{%- include "ForeignExecutorTemplate.py" %}
-
{%- else %}
{%- endmatch %}
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
index 081c6731ce..039bf76162 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterUInt16(_UniffiConverterPrimitiveInt):
return buf.read_u16()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_u16(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
index b80e75177d..1650bf9b60 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterUInt32(_UniffiConverterPrimitiveInt):
return buf.read_u32()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_u32(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
index 4b87e58547..f354545e26 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterUInt64(_UniffiConverterPrimitiveInt):
return buf.read_u64()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_u64(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
index 33026706f2..cee130b4d9 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterUInt8(_UniffiConverterPrimitiveInt):
return buf.read_u8()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_u8(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
index ef3b1bb94d..6818a8c107 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
@@ -5,27 +5,29 @@
#}
{%- macro to_ffi_call(func) -%}
- {%- match func.throws_type() -%}
- {%- when Some with (e) -%}
-_rust_call_with_error({{ e|ffi_converter_name }},
- {%- else -%}
-_rust_call(
- {%- endmatch -%}
- _UniffiLib.{{ func.ffi_func().name() }},
- {%- call arg_list_lowered(func) -%}
-)
+{%- call _to_ffi_call_with_prefix_arg("", func) %}
{%- endmacro -%}
{%- macro to_ffi_call_with_prefix(prefix, func) -%}
- {%- match func.throws_type() -%}
- {%- when Some with (e) -%}
-_rust_call_with_error(
- {{ e|ffi_converter_name }},
- {%- else -%}
+{%- call _to_ffi_call_with_prefix_arg(format!("{},", prefix), func) %}
+{%- endmacro -%}
+
+{%- macro _to_ffi_call_with_prefix_arg(prefix, func) -%}
+{%- match func.throws_type() -%}
+{%- when Some with (e) -%}
+{%- match e -%}
+{%- when Type::Enum { name, module_path } -%}
+_rust_call_with_error({{ e|ffi_converter_name }},
+{%- when Type::Object { name, module_path, imp } -%}
+_rust_call_with_error({{ e|ffi_converter_name }}__as_error,
+{%- else %}
+# unsupported error type!
+{%- endmatch %}
+{%- else -%}
_rust_call(
- {%- endmatch -%}
+{%- endmatch -%}
_UniffiLib.{{ func.ffi_func().name() }},
- {{- prefix }},
+ {{- prefix }}
{%- call arg_list_lowered(func) -%}
)
{%- endmacro -%}
@@ -37,6 +39,19 @@ _rust_call(
{%- endfor %}
{%- endmacro -%}
+{%- macro docstring_value(maybe_docstring, indent_spaces) %}
+{%- match maybe_docstring %}
+{%- when Some(docstring) %}
+{{ docstring|docstring(indent_spaces) }}
+{{ "" }}
+{%- else %}
+{%- endmatch %}
+{%- endmacro %}
+
+{%- macro docstring(defn, indent_spaces) %}
+{%- call docstring_value(defn.docstring(), indent_spaces) %}
+{%- endmacro %}
+
{#-
// Arglist as used in Python declarations of methods, functions and constructors.
// Note the var_name and type_name filters.
@@ -76,6 +91,7 @@ _rust_call(
if {{ arg.name()|var_name }} is _DEFAULT:
{{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }}
{%- endmatch %}
+ {{ arg|check_lower_fn }}({{ arg.name()|var_name }})
{% endfor -%}
{%- endmacro -%}
@@ -91,6 +107,7 @@ _rust_call(
if {{ arg.name()|var_name }} is _DEFAULT:
{{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }}
{%- endmatch %}
+ {{ arg|check_lower_fn }}({{ arg.name()|var_name }})
{% endfor -%}
{%- endmacro -%}
@@ -100,11 +117,18 @@ _rust_call(
{%- macro method_decl(py_method_name, meth) %}
{% if meth.is_async() %}
- def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
+{%- match meth.return_type() %}
+{%- when Some with (return_type) %}
+ async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}":
+{%- when None %}
+ async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None:
+{% endmatch %}
+
+ {%- call docstring(meth, 8) %}
{%- call setup_args_extra_indent(meth) %}
- return _uniffi_rust_call_async(
+ return await _uniffi_rust_call_async(
_UniffiLib.{{ meth.ffi_func().name() }}(
- self._pointer, {% call arg_list_lowered(meth) %}
+ self._uniffi_clone_pointer(), {% call arg_list_lowered(meth) %}
),
_UniffiLib.{{ meth.ffi_rust_future_poll(ci) }},
_UniffiLib.{{ meth.ffi_rust_future_complete(ci) }},
@@ -116,13 +140,7 @@ _rust_call(
{%- when None %}
lambda val: None,
{% endmatch %}
- # Error FFI converter
- {%- match meth.throws_type() %}
- {%- when Some(e) %}
- {{ e|ffi_converter_name }},
- {%- when None %}
- None,
- {%- endmatch %}
+ {% call error_ffi_converter(meth) %}
)
{%- else -%}
@@ -131,17 +149,36 @@ _rust_call(
{%- when Some with (return_type) %}
def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}":
+ {%- call docstring(meth, 8) %}
{%- call setup_args_extra_indent(meth) %}
return {{ return_type|lift_fn }}(
- {% call to_ffi_call_with_prefix("self._pointer", meth) %}
+ {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %}
)
{%- when None %}
- def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
+ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None:
+ {%- call docstring(meth, 8) %}
{%- call setup_args_extra_indent(meth) %}
- {% call to_ffi_call_with_prefix("self._pointer", meth) %}
+ {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %}
{% endmatch %}
{% endif %}
{% endmacro %}
+
+{%- macro error_ffi_converter(func) %}
+ # Error FFI converter
+{% match func.throws_type() %}
+{%- when Some(e) %}
+{%- match e -%}
+{%- when Type::Enum { name, module_path } -%}
+ {{ e|ffi_converter_name }},
+{%- when Type::Object { name, module_path, imp } -%}
+ {{ e|ffi_converter_name }}__as_error,
+{%- else %}
+ # unsupported error type!
+{%- endmatch %}
+{%- when None %}
+ None,
+{%- endmatch %}
+{% endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
index 24c3290ff7..1ccd6821c0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
@@ -1,3 +1,5 @@
+{%- call py::docstring_value(ci.namespace_docstring(), 0) %}
+
# This file was autogenerated by some hot garbage in the `uniffi` crate.
# Trust me, you don't want to mess with it!
@@ -13,6 +15,7 @@
# compile the rust component. The easiest way to ensure this is to bundle the Python
# helpers directly inline like we're doing here.
+from __future__ import annotations
import os
import sys
import ctypes
@@ -20,6 +23,9 @@ import enum
import struct
import contextlib
import datetime
+import threading
+import itertools
+import traceback
import typing
{%- if ci.has_async_fns() %}
import asyncio
@@ -34,20 +40,20 @@ _DEFAULT = object()
{% include "RustBufferTemplate.py" %}
{% include "Helpers.py" %}
-{% include "PointerManager.py" %}
+{% include "HandleMap.py" %}
{% include "RustBufferHelper.py" %}
# Contains loading, initialization code, and the FFI Function declarations.
{% include "NamespaceLibraryTemplate.py" %}
+# Public interface members begin here.
+{{ type_helper_code }}
+
# Async support
{%- if ci.has_async_fns() %}
{%- include "Async.py" %}
{%- endif %}
-# Public interface members begin here.
-{{ type_helper_code }}
-
{%- for func in ci.function_definitions() %}
{%- include "TopLevelFunctionTemplate.py" %}
{%- endfor %}
@@ -69,6 +75,9 @@ __all__ = [
{%- for c in ci.callback_interface_definitions() %}
"{{ c.name()|class_name }}",
{%- endfor %}
+ {%- if ci.has_async_fns() %}
+ "uniffi_set_event_loop",
+ {%- endif %}
]
{% import "macros.py" as py %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
index 0fcf09996f..0c23140b33 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
@@ -2,10 +2,8 @@
License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-use crate::{
- bindings::{RunScriptOptions, TargetLanguage},
- library_mode::generate_bindings,
-};
+use crate::bindings::TargetLanguage;
+use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault};
use anyhow::{Context, Result};
use camino::Utf8Path;
use std::env;
@@ -34,14 +32,18 @@ pub fn run_script(
args: Vec<String>,
_options: &RunScriptOptions,
) -> Result<()> {
- let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let script_path = Utf8Path::new(script_file).canonicalize_utf8()?;
let test_helper = UniFFITestHelper::new(crate_name)?;
let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
generate_bindings(
&cdylib_path,
None,
- &[TargetLanguage::Python],
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Python],
+ try_format_code: false,
+ },
+ None,
&out_dir,
false,
)?;
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
index 1f1bf8e299..04841b459c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
@@ -2,15 +2,39 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-use anyhow::Result;
+use anyhow::{bail, Result};
use askama::Template;
+use camino::Utf8Path;
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::collections::HashMap;
+use crate::bindings::ruby;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
+
+pub struct RubyBindingGenerator;
+impl BindingGenerator for RubyBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ ruby::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> {
+ if cdylib_name.is_none() {
+ bail!("Generate bindings for Ruby requires a cdylib, but {library_path} was given");
+ }
+ Ok(())
+ }
+}
const RESERVED_WORDS: &[&str] = &[
"alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
@@ -57,7 +81,6 @@ pub fn canonical_name(t: &Type) -> String {
Type::CallbackInterface { name, .. } => format!("CallbackInterface{name}"),
Type::Timestamp => "Timestamp".into(),
Type::Duration => "Duration".into(),
- Type::ForeignExecutor => "ForeignExecutor".into(),
// Recursive types.
// These add a prefix to the name of the underlying type.
// The component API definition cannot give names to recursive types, so as long as the
@@ -150,20 +173,20 @@ mod filters {
FfiType::UInt64 => ":uint64".to_string(),
FfiType::Float32 => ":float".to_string(),
FfiType::Float64 => ":double".to_string(),
+ FfiType::Handle => ":uint64".to_string(),
FfiType::RustArcPtr(_) => ":pointer".to_string(),
FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(),
+ FfiType::RustCallStatus => "RustCallStatus".to_string(),
FfiType::ForeignBytes => "ForeignBytes".to_string(),
- FfiType::ForeignCallback => unimplemented!("Callback interfaces are not implemented"),
- FfiType::ForeignExecutorCallback => {
- unimplemented!("Foreign executors are not implemented")
- }
- FfiType::ForeignExecutorHandle => {
- unimplemented!("Foreign executors are not implemented")
- }
- FfiType::RustFutureHandle
- | FfiType::RustFutureContinuationCallback
- | FfiType::RustFutureContinuationData => {
- unimplemented!("Async functions are not implemented")
+ FfiType::Callback(_) => unimplemented!("FFI Callbacks not implemented"),
+ // Note: this can't just be `unimplemented!()` because some of the FFI function
+ // definitions use references. Those FFI functions aren't actually used, so we just
+ // pick something that runs and makes some sense. Revisit this once the references
+ // are actually implemented.
+ FfiType::Reference(_) => ":pointer".to_string(),
+ FfiType::VoidPointer => ":pointer".to_string(),
+ FfiType::Struct(_) => {
+ unimplemented!("Structs are not implemented")
}
})
}
@@ -179,7 +202,8 @@ mod filters {
}
// use the double-quote form to match with the other languages, and quote escapes.
Literal::String(s) => format!("\"{s}\""),
- Literal::Null => "nil".into(),
+ Literal::None => "nil".into(),
+ Literal::Some { inner } => literal_rb(inner)?,
Literal::EmptySequence => "[]".into(),
Literal::EmptyMap => "{}".into(),
Literal::Enum(v, type_) => match type_ {
@@ -264,7 +288,24 @@ mod filters {
}
Type::External { .. } => panic!("No support for external types, yet"),
Type::Custom { .. } => panic!("No support for custom types, yet"),
- Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
+ })
+ }
+
+ pub fn check_lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Object { name, .. } => {
+ format!("({}.uniffi_check_lower {nm})", class_name_rb(name)?)
+ }
+ Type::Enum { .. }
+ | Type::Record { .. }
+ | Type::Optional { .. }
+ | Type::Sequence { .. }
+ | Type::Map { .. } => format!(
+ "RustBuffer.check_lower_{}({})",
+ class_name_rb(&canonical_name(type_))?,
+ nm
+ ),
+ _ => "".to_owned(),
})
}
@@ -283,7 +324,7 @@ mod filters {
Type::Boolean => format!("({nm} ? 1 : 0)"),
Type::String => format!("RustBuffer.allocFromString({nm})"),
Type::Bytes => format!("RustBuffer.allocFromBytes({nm})"),
- Type::Object { name, .. } => format!("({}._uniffi_lower {nm})", class_name_rb(name)?),
+ Type::Object { name, .. } => format!("({}.uniffi_lower {nm})", class_name_rb(name)?),
Type::CallbackInterface { .. } => {
panic!("No support for lowering callback interfaces yet")
}
@@ -300,7 +341,6 @@ mod filters {
),
Type::External { .. } => panic!("No support for lowering external types, yet"),
Type::Custom { .. } => panic!("No support for lowering custom types, yet"),
- Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
})
}
@@ -318,7 +358,7 @@ mod filters {
Type::Boolean => format!("1 == {nm}"),
Type::String => format!("{nm}.consumeIntoString"),
Type::Bytes => format!("{nm}.consumeIntoBytes"),
- Type::Object { name, .. } => format!("{}._uniffi_allocate({nm})", class_name_rb(name)?),
+ Type::Object { name, .. } => format!("{}.uniffi_allocate({nm})", class_name_rb(name)?),
Type::CallbackInterface { .. } => {
panic!("No support for lifting callback interfaces, yet")
}
@@ -341,7 +381,6 @@ mod filters {
),
Type::External { .. } => panic!("No support for lifting external types, yet"),
Type::Custom { .. } => panic!("No support for lifting custom types, yet"),
- Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
})
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
index 677c5c729b..ba2caf7380 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
@@ -2,18 +2,18 @@ class {{ obj.name()|class_name_rb }}
# A private helper for initializing instances of the class from a raw pointer,
# bypassing any initialization logic and ensuring they are GC'd properly.
- def self._uniffi_allocate(pointer)
+ def self.uniffi_allocate(pointer)
pointer.autorelease = false
inst = allocate
inst.instance_variable_set :@pointer, pointer
- ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_pointer(pointer, inst.object_id))
+ ObjectSpace.define_finalizer(inst, uniffi_define_finalizer_by_pointer(pointer, inst.object_id))
return inst
end
# A private helper for registering an object finalizer.
# N.B. it's important that this does not capture a reference
# to the actual instance, only its underlying pointer.
- def self._uniffi_define_finalizer_by_pointer(pointer, object_id)
+ def self.uniffi_define_finalizer_by_pointer(pointer, object_id)
Proc.new do |_id|
{{ ci.namespace()|class_name_rb }}.rust_call(
:{{ obj.ffi_object_free().name() }},
@@ -25,31 +25,41 @@ class {{ obj.name()|class_name_rb }}
# A private helper for lowering instances into a raw pointer.
# This does an explicit typecheck, because accidentally lowering a different type of
# object in a place where this type is expected, could lead to memory unsafety.
- def self._uniffi_lower(inst)
+ def self.uniffi_check_lower(inst)
if not inst.is_a? self
raise TypeError.new "Expected a {{ obj.name()|class_name_rb }} instance, got #{inst}"
end
- return inst.instance_variable_get :@pointer
+ end
+
+ def uniffi_clone_pointer()
+ return {{ ci.namespace()|class_name_rb }}.rust_call(
+ :{{ obj.ffi_object_clone().name() }},
+ @pointer
+ )
+ end
+
+ def self.uniffi_lower(inst)
+ return inst.uniffi_clone_pointer()
end
{%- match obj.primary_constructor() %}
{%- when Some with (cons) %}
def initialize({% call rb::arg_list_decl(cons) -%})
- {%- call rb::coerce_args_extra_indent(cons) %}
+ {%- call rb::setup_args_extra_indent(cons) %}
pointer = {% call rb::to_ffi_call(cons) %}
@pointer = pointer
- ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_pointer(pointer, self.object_id))
+ ObjectSpace.define_finalizer(self, self.class.uniffi_define_finalizer_by_pointer(pointer, self.object_id))
end
{%- when None %}
{%- endmatch %}
{% for cons in obj.alternate_constructors() -%}
def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %})
- {%- call rb::coerce_args_extra_indent(cons) %}
+ {%- call rb::setup_args_extra_indent(cons) %}
# Call the (fallible) function before creating any half-baked object instances.
# Lightly yucky way to bypass the usual "initialize" logic
# and just create a new instance with the required pointer.
- return _uniffi_allocate({% call rb::to_ffi_call(cons) %})
+ return uniffi_allocate({% call rb::to_ffi_call(cons) %})
end
{% endfor %}
@@ -58,15 +68,15 @@ class {{ obj.name()|class_name_rb }}
{%- when Some with (return_type) -%}
def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %})
- {%- call rb::coerce_args_extra_indent(meth) %}
- result = {% call rb::to_ffi_call_with_prefix("@pointer", meth) %}
+ {%- call rb::setup_args_extra_indent(meth) %}
+ result = {% call rb::to_ffi_call_with_prefix("uniffi_clone_pointer()", meth) %}
return {{ "result"|lift_rb(return_type) }}
end
{%- when None -%}
def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %})
- {%- call rb::coerce_args_extra_indent(meth) %}
- {% call rb::to_ffi_call_with_prefix("@pointer", meth) %}
+ {%- call rb::setup_args_extra_indent(meth) %}
+ {% call rb::to_ffi_call_with_prefix("uniffi_clone_pointer()", meth) %}
end
{% endmatch %}
{% endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
index c940b31060..b5a201b248 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
@@ -2,7 +2,12 @@
class {{ rec.name()|class_name_rb }}
attr_reader {% for field in rec.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %}
- def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+ def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb -}}:
+ {%- match field.default_value() %}
+ {%- when Some with(literal) %} {{ literal|literal_rb }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if loop.last %}{% else %}, {% endif -%}{% endfor %})
{%- for field in rec.fields() %}
@{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
index 8749139116..d15c0bbe76 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
@@ -163,7 +163,7 @@ class RustBufferBuilder
# The Object type {{ object_name }}.
def write_{{ canonical_type_name }}(obj)
- pointer = {{ object_name|class_name_rb}}._uniffi_lower obj
+ pointer = {{ object_name|class_name_rb}}.uniffi_lower obj
pack_into(8, 'Q>', pointer.address)
end
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
index b085dddf15..f9b0806abc 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
@@ -155,7 +155,7 @@ class RustBufferStream
def read{{ canonical_type_name }}
pointer = FFI::Pointer.new unpack_from 8, 'Q>'
- return {{ object_name|class_name_rb }}._uniffi_allocate(pointer)
+ return {{ object_name|class_name_rb }}.uniffi_allocate(pointer)
end
{% when Type::Enum { name, module_path } -%}
@@ -237,7 +237,7 @@ class RustBufferStream
def read{{ canonical_type_name }}
{{ rec.name()|class_name_rb }}.new(
{%- for field in rec.fields() %}
- read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %}
+ {{ field.name()|var_name_rb }}: read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %}
{%- endfor %}
)
end
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
index 0194c9666d..452d9831cd 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
@@ -1,6 +1,6 @@
class RustBuffer < FFI::Struct
- layout :capacity, :int32,
- :len, :int32,
+ layout :capacity, :uint64,
+ :len, :uint64,
:data, :pointer
def self.alloc(size)
@@ -128,6 +128,12 @@ class RustBuffer < FFI::Struct
{%- let rec = ci|get_record_definition(record_name) -%}
# The Record type {{ record_name }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ {%- for field in rec.fields() %}
+ {{ "v.{}"|format(field.name()|var_name_rb)|check_lower_rb(field.as_type().borrow()) }}
+ {%- endfor %}
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
@@ -146,6 +152,19 @@ class RustBuffer < FFI::Struct
{%- let e = ci|get_enum_definition(enum_name) -%}
# The Enum type {{ enum_name }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ {%- if !e.is_flat() %}
+ {%- for variant in e.variants() %}
+ if v.{{ variant.name()|var_name_rb }}?
+ {%- for field in variant.fields() %}
+ {{ "v.{}"|format(field.name())|check_lower_rb(field.as_type().borrow()) }}
+ {%- endfor %}
+ return
+ end
+ {%- endfor %}
+ {%- endif %}
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
@@ -163,6 +182,12 @@ class RustBuffer < FFI::Struct
{% when Type::Optional { inner_type } -%}
# The Optional<T> type for {{ canonical_name(inner_type) }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ if not v.nil?
+ {{ "v"|check_lower_rb(inner_type.borrow()) }}
+ end
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
@@ -179,6 +204,12 @@ class RustBuffer < FFI::Struct
{% when Type::Sequence { inner_type } -%}
# The Sequence<T> type for {{ canonical_name(inner_type) }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ v.each do |item|
+ {{ "item"|check_lower_rb(inner_type.borrow()) }}
+ end
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
@@ -195,6 +226,13 @@ class RustBuffer < FFI::Struct
{% when Type::Map { key_type: k, value_type: inner_type } -%}
# The Map<T> type for {{ canonical_name(inner_type) }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ v.each do |k, v|
+ {{ "k"|check_lower_rb(k.borrow()) }}
+ {{ "v"|check_lower_rb(inner_type.borrow()) }}
+ end
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
index 13214cf31b..b6dce0effa 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
@@ -2,7 +2,7 @@
{%- when Some with (return_type) %}
def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%})
- {%- call rb::coerce_args(func) %}
+ {%- call rb::setup_args(func) %}
result = {% call rb::to_ffi_call(func) %}
return {{ "result"|lift_rb(return_type) }}
end
@@ -10,7 +10,7 @@ end
{% when None %}
def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%})
- {%- call rb::coerce_args(func) %}
+ {%- call rb::setup_args(func) %}
{% call rb::to_ffi_call(func) %}
end
{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
index 8dc3e5e613..59fa4ef4cc 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
@@ -60,14 +60,16 @@
[{%- for arg in func.arguments() -%}{{ arg.type_().borrow()|type_ffi }}, {% endfor -%} RustCallStatus.by_ref]
{%- endmacro -%}
-{%- macro coerce_args(func) %}
+{%- macro setup_args(func) %}
{%- for arg in func.arguments() %}
- {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) -}}
+ {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }}
+ {{ arg.name()|check_lower_rb(arg.as_type().borrow()) }}
{% endfor -%}
{%- endmacro -%}
-{%- macro coerce_args_extra_indent(func) %}
- {%- for arg in func.arguments() %}
+{%- macro setup_args_extra_indent(meth) %}
+ {%- for arg in meth.arguments() %}
{{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }}
+ {{ arg.name()|check_lower_rb(arg.as_type().borrow()) }}
{%- endfor %}
{%- endmacro -%}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
index 03da37d567..88f770b9dc 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
@@ -4,6 +4,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this
use crate::bindings::TargetLanguage;
use crate::library_mode::generate_bindings;
+use crate::BindingGeneratorDefault;
use anyhow::{bail, Context, Result};
use camino::Utf8Path;
use std::env;
@@ -30,11 +31,21 @@ pub fn test_script_command(
fixture_name: &str,
script_file: &str,
) -> Result<Command> {
- let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let script_path = Utf8Path::new(script_file).canonicalize_utf8()?;
let test_helper = UniFFITestHelper::new(fixture_name)?;
let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
- generate_bindings(&cdylib_path, None, &[TargetLanguage::Ruby], &out_dir, false)?;
+ generate_bindings(
+ &cdylib_path,
+ None,
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Ruby],
+ try_format_code: false,
+ },
+ None,
+ &out_dir,
+ false,
+ )?;
let rubypath = env::var_os("RUBYLIB").unwrap_or_else(|| OsString::from(""));
let rubypath = env::join_paths(
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs
index 5d8b37e0af..dab89e0259 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs
@@ -6,21 +6,25 @@ use super::CodeType;
#[derive(Debug)]
pub struct CallbackInterfaceCodeType {
- id: String,
+ name: String,
}
impl CallbackInterfaceCodeType {
- pub fn new(id: String) -> Self {
- Self { id }
+ pub fn new(name: String) -> Self {
+ Self { name }
}
}
impl CodeType for CallbackInterfaceCodeType {
fn type_label(&self) -> String {
- super::SwiftCodeOracle.class_name(&self.id)
+ super::SwiftCodeOracle.class_name(&self.name)
}
fn canonical_name(&self) -> String {
format!("CallbackInterface{}", self.type_label())
}
+
+ fn initialization_fn(&self) -> Option<String> {
+ Some(format!("uniffiCallbackInit{}", self.name))
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
index 8e6dddf3f9..d89fdfd386 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
@@ -30,8 +30,9 @@ impl CodeType for OptionalCodeType {
fn literal(&self, literal: &Literal) -> String {
match literal {
- Literal::Null => "nil".into(),
- _ => super::SwiftCodeOracle.find(&self.inner).literal(literal),
+ Literal::None => "nil".into(),
+ Literal::Some { inner } => super::SwiftCodeOracle.find(&self.inner).literal(inner),
+ _ => panic!("Invalid literal for Optional type: {literal:?}"),
}
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs
deleted file mode 100644
index b488b004cf..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use super::CodeType;
-
-#[derive(Debug)]
-pub struct ForeignExecutorCodeType;
-
-impl CodeType for ForeignExecutorCodeType {
- fn type_label(&self) -> String {
- // On Swift, we define a struct to represent a ForeignExecutor
- "UniFfiForeignExecutor".into()
- }
-
- fn canonical_name(&self) -> String {
- "ForeignExecutor".into()
- }
-
- fn initialization_fn(&self) -> Option<String> {
- Some("uniffiInitForeignExecutor".into())
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
index 0b6728ba84..3960b7aae1 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
@@ -17,7 +17,7 @@ impl ExternalCodeType {
impl CodeType for ExternalCodeType {
fn type_label(&self) -> String {
- self.name.clone()
+ super::SwiftCodeOracle.class_name(&self.name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
index 12db4afc66..16c1625123 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
@@ -10,25 +10,49 @@ use std::fmt::Debug;
use anyhow::{Context, Result};
use askama::Template;
-use heck::{ToLowerCamelCase, ToUpperCamelCase};
+use camino::Utf8Path;
+use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
use serde::{Deserialize, Serialize};
use super::Bindings;
use crate::backend::TemplateExpression;
+use crate::bindings::swift;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
mod callback_interface;
mod compounds;
mod custom;
mod enum_;
-mod executor;
mod external;
mod miscellany;
mod object;
mod primitives;
mod record;
+pub struct SwiftBindingGenerator;
+impl BindingGenerator for SwiftBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ swift::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(
+ &self,
+ _library_path: &Utf8Path,
+ _cdylib_name: Option<&str>,
+ ) -> Result<()> {
+ Ok(())
+ }
+}
+
/// A trait tor the implementation.
trait CodeType: Debug {
/// The language specific label used to reference this type. This will be used in
@@ -196,6 +220,8 @@ pub struct Config {
ffi_module_filename: Option<String>,
generate_module_map: Option<bool>,
omit_argument_labels: Option<bool>,
+ generate_immutable_records: Option<bool>,
+ experimental_sendable_value_types: Option<bool>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
}
@@ -261,6 +287,16 @@ impl Config {
pub fn omit_argument_labels(&self) -> bool {
self.omit_argument_labels.unwrap_or(false)
}
+
+ /// Whether to generate immutable records (`let` instead of `var`)
+ pub fn generate_immutable_records(&self) -> bool {
+ self.generate_immutable_records.unwrap_or(false)
+ }
+
+ /// Whether to mark value types as 'Sendable'
+ pub fn experimental_sendable_value_types(&self) -> bool {
+ self.experimental_sendable_value_types.unwrap_or(false)
+ }
}
impl BindingsConfig for Config {
@@ -400,7 +436,6 @@ pub struct SwiftWrapper<'a> {
ci: &'a ComponentInterface,
type_helper_code: String,
type_imports: BTreeSet<String>,
- has_async_fns: bool,
}
impl<'a> SwiftWrapper<'a> {
pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
@@ -412,7 +447,6 @@ impl<'a> SwiftWrapper<'a> {
ci,
type_helper_code,
type_imports,
- has_async_fns: ci.has_async_fns(),
}
}
@@ -425,10 +459,6 @@ impl<'a> SwiftWrapper<'a> {
.iter_types()
.map(|t| SwiftCodeOracle.find(t))
.filter_map(|ct| ct.initialization_fn())
- .chain(
- self.has_async_fns
- .then(|| "uniffiInitContinuationCallback".into()),
- )
.collect()
}
}
@@ -464,12 +494,11 @@ impl SwiftCodeOracle {
Type::Duration => Box::new(miscellany::DurationCodeType),
Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
- Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
+ Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)),
Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
Type::CallbackInterface { name, .. } => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
}
- Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional { inner_type } => {
Box::new(compounds::OptionalCodeType::new(*inner_type))
}
@@ -509,7 +538,22 @@ impl SwiftCodeOracle {
nm.to_string().to_lower_camel_case()
}
- fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String {
+ /// Get the idiomatic Swift rendering of an FFI callback function name
+ fn ffi_callback_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of an FFI struct name
+ fn ffi_struct_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of an if guard name
+ fn if_guard_name(&self, nm: &str) -> String {
+ format!("UNIFFI_FFIDEF_{}", nm.to_shouty_snake_case())
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::Int8 => "Int8".into(),
FfiType::UInt8 => "UInt8".into(),
@@ -521,40 +565,74 @@ impl SwiftCodeOracle {
FfiType::UInt64 => "UInt64".into(),
FfiType::Float32 => "Float".into(),
FfiType::Float64 => "Double".into(),
+ FfiType::Handle => "UInt64".into(),
FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(),
FfiType::RustBuffer(_) => "RustBuffer".into(),
+ FfiType::RustCallStatus => "RustCallStatus".into(),
FfiType::ForeignBytes => "ForeignBytes".into(),
- FfiType::ForeignCallback => "ForeignCallback".into(),
- FfiType::ForeignExecutorHandle => "Int".into(),
- FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(),
- FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(),
- FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
- "UnsafeMutableRawPointer".into()
+ // Note: @escaping is required for Swift versions before 5.7 for callbacks passed into
+ // async functions. Swift 5.7 and later does not require it. We should probably remove
+ // it once we upgrade our minimum requirement to 5.7 or later.
+ FfiType::Callback(name) => format!("@escaping {}", self.ffi_callback_name(name)),
+ FfiType::Struct(name) => self.ffi_struct_name(name),
+ FfiType::Reference(inner) => {
+ format!("UnsafeMutablePointer<{}>", self.ffi_type_label(inner))
}
+ FfiType::VoidPointer => "UnsafeMutableRawPointer".into(),
}
}
- fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
- match ffi_type {
- FfiType::ForeignCallback
- | FfiType::ForeignExecutorCallback
- | FfiType::RustFutureHandle
- | FfiType::RustFutureContinuationCallback
- | FfiType::RustFutureContinuationData => {
- format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type))
- }
- _ => self.ffi_type_label_raw(ffi_type),
+ /// Default values for FFI types
+ ///
+ /// Used to set a default return value when returning an error
+ fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String {
+ match return_type {
+ Some(t) => match t {
+ FfiType::UInt8
+ | FfiType::Int8
+ | FfiType::UInt16
+ | FfiType::Int16
+ | FfiType::UInt32
+ | FfiType::Int32
+ | FfiType::UInt64
+ | FfiType::Int64 => "0".to_owned(),
+ FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(),
+ FfiType::RustArcPtr(_) => "nil".to_owned(),
+ FfiType::RustBuffer(_) => "RustBuffer.empty()".to_owned(),
+ _ => unimplemented!("FFI return type: {t:?}"),
+ },
+ // When we need to use a value for void returns, we use a `u8` placeholder
+ None => "0".to_owned(),
}
}
fn ffi_canonical_name(&self, ffi_type: &FfiType) -> String {
- self.ffi_type_label_raw(ffi_type)
+ self.ffi_type_label(ffi_type)
+ }
+
+ /// Get the name of the protocol and class name for an object.
+ ///
+ /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that.
+ /// Otherwise, the class name is the object name and the protocol name is derived from that.
+ ///
+ /// This split determines what types `FfiConverter.lower()` inputs. If we support callback
+ /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower
+ /// only lowers the concrete class.
+ fn object_names(&self, obj: &Object) -> (String, String) {
+ let class_name = self.class_name(obj.name());
+ if obj.has_callback_interface() {
+ let impl_name = format!("{class_name}Impl");
+ (class_name, impl_name)
+ } else {
+ (format!("{class_name}Protocol"), class_name)
+ }
}
}
pub mod filters {
use super::*;
pub use crate::backend::filters::*;
+ use uniffi_meta::LiteralMetadata;
fn oracle() -> &'static SwiftCodeOracle {
&SwiftCodeOracle
@@ -564,6 +642,13 @@ pub mod filters {
Ok(oracle().find(&as_type.as_type()).type_label())
}
+ pub fn return_type_name(as_type: Option<&impl AsType>) -> Result<String, askama::Error> {
+ Ok(match as_type {
+ Some(as_type) => oracle().find(&as_type.as_type()).type_label(),
+ None => "()".to_owned(),
+ })
+ }
+
pub fn canonical_name(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(oracle().find(&as_type.as_type()).canonical_name())
}
@@ -572,6 +657,15 @@ pub mod filters {
Ok(oracle().find(&as_type.as_type()).ffi_converter_name())
}
+ pub fn ffi_error_converter_name(as_type: &impl AsType) -> Result<String, askama::Error> {
+ // special handling for types used as errors.
+ let mut name = oracle().find(&as_type.as_type()).ffi_converter_name();
+ if matches!(&as_type.as_type(), Type::Object { .. }) {
+ name.push_str("__as_error")
+ }
+ Ok(name)
+ }
+
pub fn lower_fn(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(oracle().find(&as_type.as_type()).lower())
}
@@ -595,6 +689,16 @@ pub mod filters {
Ok(oracle().find(&as_type.as_type()).literal(literal))
}
+ // Get the idiomatic Swift rendering of an individual enum variant's discriminant
+ pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> {
+ let literal = e.variant_discr(*index).expect("invalid index");
+ match literal {
+ LiteralMetadata::UInt(v, _, _) => Ok(v.to_string()),
+ LiteralMetadata::Int(v, _, _) => Ok(v.to_string()),
+ _ => unreachable!("expected an UInt!"),
+ }
+ }
+
/// Get the Swift type for an FFIType
pub fn ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> {
Ok(oracle().ffi_type_label(ffi_type))
@@ -604,6 +708,10 @@ pub mod filters {
Ok(oracle().ffi_canonical_name(ffi_type))
}
+ pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_default_value(return_type.as_ref()))
+ }
+
/// Like `ffi_type_name`, but used in `BridgingHeaderTemplate.h` which uses a slightly different
/// names.
pub fn header_ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> {
@@ -618,18 +726,17 @@ pub mod filters {
FfiType::UInt64 => "uint64_t".into(),
FfiType::Float32 => "float".into(),
FfiType::Float64 => "double".into(),
+ FfiType::Handle => "uint64_t".into(),
FfiType::RustArcPtr(_) => "void*_Nonnull".into(),
FfiType::RustBuffer(_) => "RustBuffer".into(),
+ FfiType::RustCallStatus => "RustCallStatus".into(),
FfiType::ForeignBytes => "ForeignBytes".into(),
- FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(),
- FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(),
- FfiType::ForeignExecutorHandle => "size_t".into(),
- FfiType::RustFutureContinuationCallback => {
- "UniFfiRustFutureContinuation _Nonnull".into()
- }
- FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
- "void* _Nonnull".into()
+ FfiType::Callback(name) => {
+ format!("{} _Nonnull", SwiftCodeOracle.ffi_callback_name(name))
}
+ FfiType::Struct(name) => SwiftCodeOracle.ffi_struct_name(name),
+ FfiType::Reference(inner) => format!("{}* _Nonnull", header_ffi_type_name(inner)?),
+ FfiType::VoidPointer => "void* _Nonnull".into(),
})
}
@@ -664,6 +771,30 @@ pub mod filters {
Ok(oracle().enum_variant_name(nm))
}
+ /// Get the idiomatic Swift rendering of an FFI callback function name
+ pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_callback_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of an FFI struct name
+ pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_struct_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of an if guard name
+ pub fn if_guard_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().if_guard_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of docstring
+ pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
+ let middle = textwrap::indent(&textwrap::dedent(docstring), " * ");
+ let wrapped = format!("/**\n{middle}\n */");
+
+ let spaces = usize::try_from(*spaces).unwrap_or_default();
+ Ok(textwrap::indent(&wrapped, &" ".repeat(spaces)))
+ }
+
pub fn error_handler(result: &ResultType) -> Result<String, askama::Error> {
Ok(match &result.throws_type {
Some(t) => format!("{}.lift", ffi_converter_name(t)?),
@@ -685,4 +816,8 @@ pub mod filters {
}
))
}
+
+ pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> {
+ Ok(SwiftCodeOracle.object_names(obj))
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs
index ea140c998d..d4497a7b19 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs
@@ -3,24 +3,32 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::CodeType;
+use crate::interface::ObjectImpl;
#[derive(Debug)]
pub struct ObjectCodeType {
- id: String,
+ name: String,
+ imp: ObjectImpl,
}
impl ObjectCodeType {
- pub fn new(id: String) -> Self {
- Self { id }
+ pub fn new(name: String, imp: ObjectImpl) -> Self {
+ Self { name, imp }
}
}
impl CodeType for ObjectCodeType {
fn type_label(&self) -> String {
- super::SwiftCodeOracle.class_name(&self.id)
+ super::SwiftCodeOracle.class_name(&self.name)
}
fn canonical_name(&self) -> String {
- format!("Type{}", self.id)
+ format!("Type{}", self.name)
+ }
+
+ fn initialization_fn(&self) -> Option<String> {
+ self.imp
+ .has_callback_interface()
+ .then(|| format!("uniffiCallbackInit{}", self.name))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
index 86424658a3..e0c670520e 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
@@ -9,7 +9,11 @@ use paste::paste;
fn render_literal(literal: &Literal) -> String {
fn typed_number(type_: &Type, num_str: String) -> String {
- match type_ {
+ let unwrapped_type = match type_ {
+ Type::Optional { inner_type } => inner_type,
+ t => t,
+ };
+ match unwrapped_type {
// special case Int32.
Type::Int32 => num_str,
// otherwise use constructor e.g. UInt8(x)
@@ -29,7 +33,7 @@ fn render_literal(literal: &Literal) -> String {
super::SwiftCodeOracle.find(type_).type_label()
)
}
- _ => panic!("Unexpected literal: {num_str} is not a number"),
+ _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"),
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift
index 695208861d..e16f3108e1 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift
@@ -1,11 +1,13 @@
private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0
private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1
+fileprivate let uniffiContinuationHandleMap = UniffiHandleMap<UnsafeContinuation<Int8, Never>>()
+
fileprivate func uniffiRustCallAsync<F, T>(
- rustFutureFunc: () -> UnsafeMutableRawPointer,
- pollFunc: (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> (),
- completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer<RustCallStatus>) -> F,
- freeFunc: (UnsafeMutableRawPointer) -> (),
+ rustFutureFunc: () -> UInt64,
+ pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (),
+ completeFunc: (UInt64, UnsafeMutablePointer<RustCallStatus>) -> F,
+ freeFunc: (UInt64) -> (),
liftFunc: (F) throws -> T,
errorHandler: ((RustBuffer) throws -> Error)?
) async throws -> T {
@@ -19,7 +21,11 @@ fileprivate func uniffiRustCallAsync<F, T>(
var pollResult: Int8;
repeat {
pollResult = await withUnsafeContinuation {
- pollFunc(rustFuture, ContinuationHolder($0).toOpaque())
+ pollFunc(
+ rustFuture,
+ uniffiFutureContinuationCallback,
+ uniffiContinuationHandleMap.insert(obj: $0)
+ )
}
} while pollResult != UNIFFI_RUST_FUTURE_POLL_READY
@@ -31,32 +37,80 @@ fileprivate func uniffiRustCallAsync<F, T>(
// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They
// lift the return value or error and resume the suspended function.
-fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) {
- ContinuationHolder.fromOpaque(ptr).resume(pollResult)
+fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) {
+ if let continuation = try? uniffiContinuationHandleMap.remove(handle: handle) {
+ continuation.resume(returning: pollResult)
+ } else {
+ print("uniffiFutureContinuationCallback invalid handle")
+ }
}
-// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across
-// the FFI
-fileprivate class ContinuationHolder {
- let continuation: UnsafeContinuation<Int8, Never>
-
- init(_ continuation: UnsafeContinuation<Int8, Never>) {
- self.continuation = continuation
+{%- if ci.has_async_callback_interface_definition() %}
+private func uniffiTraitInterfaceCallAsync<T>(
+ makeCall: @escaping () async throws -> T,
+ handleSuccess: @escaping (T) -> (),
+ handleError: @escaping (Int8, RustBuffer) -> ()
+) -> UniffiForeignFuture {
+ let task = Task {
+ do {
+ handleSuccess(try await makeCall())
+ } catch {
+ handleError(CALL_UNEXPECTED_ERROR, {{ Type::String.borrow()|lower_fn }}(String(describing: error)))
+ }
}
+ let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task)
+ return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree)
- func resume(_ pollResult: Int8) {
- self.continuation.resume(returning: pollResult)
- }
+}
- func toOpaque() -> UnsafeMutableRawPointer {
- return Unmanaged<ContinuationHolder>.passRetained(self).toOpaque()
+private func uniffiTraitInterfaceCallAsyncWithError<T, E>(
+ makeCall: @escaping () async throws -> T,
+ handleSuccess: @escaping (T) -> (),
+ handleError: @escaping (Int8, RustBuffer) -> (),
+ lowerError: @escaping (E) -> RustBuffer
+) -> UniffiForeignFuture {
+ let task = Task {
+ do {
+ handleSuccess(try await makeCall())
+ } catch let error as E {
+ handleError(CALL_ERROR, lowerError(error))
+ } catch {
+ handleError(CALL_UNEXPECTED_ERROR, {{ Type::String.borrow()|lower_fn }}(String(describing: error)))
+ }
}
+ let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task)
+ return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree)
+}
+
+// Borrow the callback handle map implementation to store foreign future handles
+// TODO: consolidate the handle-map code (https://github.com/mozilla/uniffi-rs/pull/1823)
+fileprivate var UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = UniffiHandleMap<UniffiForeignFutureTask>()
+
+// Protocol for tasks that handle foreign futures.
+//
+// Defining a protocol allows all tasks to be stored in the same handle map. This can't be done
+// with the task object itself, since has generic parameters.
+protocol UniffiForeignFutureTask {
+ func cancel()
+}
+
+extension Task: UniffiForeignFutureTask {}
- static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder {
- return Unmanaged<ContinuationHolder>.fromOpaque(ptr).takeRetainedValue()
+private func uniffiForeignFutureFree(handle: UInt64) {
+ do {
+ let task = try UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle: handle)
+ // Set the cancellation flag on the task. If it's still running, the code can check the
+ // cancellation flag or call `Task.checkCancellation()`. If the task has completed, this is
+ // a no-op.
+ task.cancel()
+ } catch {
+ print("uniffiForeignFutureFree: handle missing from handlemap")
}
}
-fileprivate func uniffiInitContinuationCallback() {
- {{ ci.ffi_rust_future_continuation_callback_set().name() }}(uniffiFutureContinuationCallback)
+// For testing
+public func uniffiForeignFutureHandleCount{{ ci.namespace()|class_name }}() -> Int {
+ UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.count
}
+
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
index 87698e359f..89d98594d3 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
@@ -24,25 +24,11 @@
typedef struct RustBuffer
{
- int32_t capacity;
- int32_t len;
+ uint64_t capacity;
+ uint64_t len;
uint8_t *_Nullable data;
} RustBuffer;
-typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull);
-
-// Task defined in Rust that Swift executes
-typedef void (*UniFfiRustTaskCallback)(const void * _Nullable, int8_t);
-
-// Callback to execute Rust tasks using a Swift Task
-//
-// Args:
-// executor: ForeignExecutor lowered into a size_t value
-// delay: Delay in MS
-// task: UniFfiRustTaskCallback to call
-// task_data: data to pass the task callback
-typedef int8_t (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable);
-
typedef struct ForeignBytes
{
int32_t len;
@@ -59,11 +45,29 @@ typedef struct RustCallStatus {
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
#endif // def UNIFFI_SHARED_H
-// Continuation callback for UniFFI Futures
-typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t);
-
-// Scaffolding functions
-{%- for func in ci.iter_ffi_function_definitions() %}
+{%- for def in ci.ffi_definitions() %}
+#ifndef {{ def.name()|if_guard_name }}
+#define {{ def.name()|if_guard_name }}
+{%- match def %}
+{% when FfiDefinition::CallbackFunction(callback) %}
+typedef
+ {%- match callback.return_type() %}{% when Some(return_type) %} {{ return_type|header_ffi_type_name }} {% when None %} void {% endmatch -%}
+ (*{{ callback.name()|ffi_callback_name }})(
+ {%- for arg in callback.arguments() -%}
+ {{ arg.type_().borrow()|header_ffi_type_name }}
+ {%- if !loop.last || callback.has_rust_call_status_arg() %}, {% endif %}
+ {%- endfor -%}
+ {%- if callback.has_rust_call_status_arg() %}
+ RustCallStatus *_Nonnull uniffiCallStatus
+ {%- endif %}
+ );
+{% when FfiDefinition::Struct(struct) %}
+typedef struct {{ struct.name()|ffi_struct_name }} {
+ {%- for field in struct.fields() %}
+ {{ field.type_().borrow()|header_ffi_type_name }} {{ field.name()|var_name }};
+ {%- endfor %}
+} {{ struct.name()|ffi_struct_name }};
+{% when FfiDefinition::Function(func) %}
{% match func.return_type() -%}{%- when Some with (type_) %}{{ type_|header_ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}(
{%- if func.arguments().len() > 0 %}
{%- for arg in func.arguments() %}
@@ -74,6 +78,8 @@ typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t);
{%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{%- else %}void{% endif %}
{% endif %}
);
+{%- endmatch %}
+#endif
{%- endfor %}
{% import "macros.swift" as swift %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift
new file mode 100644
index 0000000000..74ee372642
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift
@@ -0,0 +1,113 @@
+{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %}
+{%- let trait_impl=format!("UniffiCallbackInterface{}", name) %}
+
+// Put the implementation in a struct so we don't pollute the top-level namespace
+fileprivate struct {{ trait_impl }} {
+
+ // Create the VTable using a series of closures.
+ // Swift automatically converts these into C callback functions.
+ static var vtable: {{ vtable|ffi_type_name }} = {{ vtable|ffi_type_name }}(
+ {%- for (ffi_callback, meth) in vtable_methods %}
+ {{ meth.name()|fn_name }}: { (
+ {%- for arg in ffi_callback.arguments() %}
+ {{ arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name }}{% if !loop.last || ffi_callback.has_rust_call_status_arg() %},{% endif %}
+ {%- endfor -%}
+ {%- if ffi_callback.has_rust_call_status_arg() %}
+ uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
+ {%- endif %}
+ ) in
+ let makeCall = {
+ () {% if meth.is_async() %}async {% endif %}throws -> {% match meth.return_type() %}{% when Some(t) %}{{ t|type_name }}{% when None %}(){% endmatch %} in
+ guard let uniffiObj = try? {{ ffi_converter_name }}.handleMap.get(handle: uniffiHandle) else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return {% if meth.throws() %}try {% endif %}{% if meth.is_async() %}await {% endif %}uniffiObj.{{ meth.name()|fn_name }}(
+ {%- for arg in meth.arguments() %}
+ {% if !config.omit_argument_labels() %} {{ arg.name()|arg_name }}: {% endif %}try {{ arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %}
+ {%- endfor %}
+ )
+ }
+ {%- if !meth.is_async() %}
+
+ {% match meth.return_type() %}
+ {%- when Some(t) %}
+ let writeReturn = { uniffiOutReturn.pointee = {{ t|lower_fn }}($0) }
+ {%- when None %}
+ let writeReturn = { () }
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffiTraitInterfaceCall(
+ callStatus: uniffiCallStatus,
+ makeCall: makeCall,
+ writeReturn: writeReturn
+ )
+ {%- when Some(error_type) %}
+ uniffiTraitInterfaceCallWithError(
+ callStatus: uniffiCallStatus,
+ makeCall: makeCall,
+ writeReturn: writeReturn,
+ lowerError: {{ error_type|lower_fn }}
+ )
+ {%- endmatch %}
+ {%- else %}
+
+ let uniffiHandleSuccess = { (returnValue: {{ meth.return_type()|return_type_name }}) in
+ uniffiFutureCallback(
+ uniffiCallbackData,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ returnValue: {{ return_type|lower_fn }}(returnValue),
+ {%- when None %}
+ {%- endmatch %}
+ callStatus: RustCallStatus()
+ )
+ )
+ }
+ let uniffiHandleError = { (statusCode, errorBuf) in
+ uniffiFutureCallback(
+ uniffiCallbackData,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ returnValue: {{ meth.return_type().map(FfiType::from)|ffi_default_value }},
+ {%- when None %}
+ {%- endmatch %}
+ callStatus: RustCallStatus(code: statusCode, errorBuf: errorBuf)
+ )
+ )
+ }
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ let uniffiForeignFuture = uniffiTraitInterfaceCallAsync(
+ makeCall: makeCall,
+ handleSuccess: uniffiHandleSuccess,
+ handleError: uniffiHandleError
+ )
+ {%- when Some(error_type) %}
+ let uniffiForeignFuture = uniffiTraitInterfaceCallAsyncWithError(
+ makeCall: makeCall,
+ handleSuccess: uniffiHandleSuccess,
+ handleError: uniffiHandleError,
+ lowerError: {{ error_type|lower_fn }}
+ )
+ {%- endmatch %}
+ uniffiOutReturn.pointee = uniffiForeignFuture
+ {%- endif %}
+ },
+ {%- endfor %}
+ uniffiFree: { (uniffiHandle: UInt64) -> () in
+ let result = try? {{ ffi_converter_name }}.handleMap.remove(handle: uniffiHandle)
+ if result == nil {
+ print("Uniffi callback interface {{ name }}: handle missing in uniffiFree")
+ }
+ }
+ )
+}
+
+private func {{ callback_init }}() {
+ {{ ffi_init_callback.name() }}(&{{ trait_impl }}.vtable)
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
index 9ae62d1667..5863c2ad41 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
@@ -1,60 +1,3 @@
-fileprivate extension NSLock {
- func withLock<T>(f: () throws -> T) rethrows -> T {
- self.lock()
- defer { self.unlock() }
- return try f()
- }
-}
-
-fileprivate typealias UniFFICallbackHandle = UInt64
-fileprivate class UniFFICallbackHandleMap<T> {
- private var leftMap: [UniFFICallbackHandle: T] = [:]
- private var counter: [UniFFICallbackHandle: UInt64] = [:]
- private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:]
-
- private let lock = NSLock()
- private var currentHandle: UniFFICallbackHandle = 0
- private let stride: UniFFICallbackHandle = 1
-
- func insert(obj: T) -> UniFFICallbackHandle {
- lock.withLock {
- let id = ObjectIdentifier(obj as AnyObject)
- let handle = rightMap[id] ?? {
- currentHandle += stride
- let handle = currentHandle
- leftMap[handle] = obj
- rightMap[id] = handle
- return handle
- }()
- counter[handle] = (counter[handle] ?? 0) + 1
- return handle
- }
- }
-
- func get(handle: UniFFICallbackHandle) -> T? {
- lock.withLock {
- leftMap[handle]
- }
- }
-
- func delete(handle: UniFFICallbackHandle) {
- remove(handle: handle)
- }
-
- @discardableResult
- func remove(handle: UniFFICallbackHandle) -> T? {
- lock.withLock {
- defer { counter[handle] = (counter[handle] ?? 1) - 1 }
- guard counter[handle] == 1 else { return leftMap[handle] }
- let obj = leftMap.removeValue(forKey: handle)
- if let obj = obj {
- rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject))
- }
- return obj
- }
- }
-}
-
// Magic number for the Rust proxy to call using the same mechanism as every other method,
// to free the callback once it's dropped by Rust.
private let IDX_CALLBACK_FREE: Int32 = 0
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
index aec8ded930..7aa1cca9b2 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
@@ -1,150 +1,39 @@
{%- let cbi = ci|get_callback_interface_definition(name) %}
-{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
-{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %}
-
-// Declaration and FfiConverters for {{ type_name }} Callback Interface
-
-public protocol {{ type_name }} : AnyObject {
- {% for meth in cbi.methods() -%}
- func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
- {%- else -%}
- {%- endmatch %}
- {% endfor %}
-}
-
-// The ForeignCallback that is passed to Rust.
-fileprivate let {{ foreign_callback }} : ForeignCallback =
- { (handle: UniFFICallbackHandle, method: Int32, argsData: UnsafePointer<UInt8>, argsLen: Int32, out_buf: UnsafeMutablePointer<RustBuffer>) -> Int32 in
- {% for meth in cbi.methods() -%}
- {%- let method_name = format!("invoke_{}", meth.name())|fn_name %}
-
- func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer<UInt8>, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer<RustBuffer>) throws -> Int32 {
- {%- if meth.arguments().len() > 0 %}
- var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen)))
- {%- endif %}
-
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- func makeCall() throws -> Int32 {
- let result = {% if meth.throws() %} try{% endif %} swiftCallbackInterface.{{ meth.name()|fn_name }}(
- {% for arg in meth.arguments() -%}
- {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader)
- {%- if !loop.last %}, {% endif %}
- {% endfor -%}
- )
- var writer = [UInt8]()
- {{ return_type|write_fn }}(result, into: &writer)
- out_buf.pointee = RustBuffer(bytes: writer)
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- when None %}
- func makeCall() throws -> Int32 {
- try swiftCallbackInterface.{{ meth.name()|fn_name }}(
- {% for arg in meth.arguments() -%}
- {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader)
- {%- if !loop.last %}, {% endif %}
- {% endfor -%}
- )
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- endmatch %}
-
- {%- match meth.throws_type() %}
- {%- when None %}
- return try makeCall()
- {%- when Some(error_type) %}
- do {
- return try makeCall()
- } catch let error as {{ error_type|type_name }} {
- out_buf.pointee = {{ error_type|lower_fn }}(error)
- return UNIFFI_CALLBACK_ERROR
- }
- {%- endmatch %}
- }
- {%- endfor %}
-
-
- switch method {
- case IDX_CALLBACK_FREE:
- {{ ffi_converter_name }}.drop(handle: handle)
- // Sucessful return
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- return UNIFFI_CALLBACK_SUCCESS
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
- case {{ loop.index }}:
- let cb: {{ cbi|type_name }}
- do {
- cb = try {{ ffi_converter_name }}.lift(handle)
- } catch {
- out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("{{ cbi.name() }}: Invalid handle")
- return UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- do {
- return try {{ method_name }}(cb, argsData, argsLen, out_buf)
- } catch let error {
- out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
- return UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- {% endfor %}
- // This should never happen, because an out of bounds method index won't
- // ever be used. Once we can catch errors, we should return an InternalError.
- // https://github.com/mozilla/uniffi-rs/issues/351
- default:
- // An unexpected error happened.
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- return UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
-}
+{%- let callback_handler = format!("uniffiCallbackHandler{}", name) %}
+{%- let callback_init = format!("uniffiCallbackInit{}", name) %}
+{%- let methods = cbi.methods() %}
+{%- let protocol_name = type_name.clone() %}
+{%- let protocol_docstring = cbi.docstring() %}
+{%- let vtable = cbi.vtable() %}
+{%- let vtable_methods = cbi.vtable_methods() %}
+{%- let ffi_init_callback = cbi.ffi_init_callback() %}
+
+{% include "Protocol.swift" %}
+{% include "CallbackInterfaceImpl.swift" %}
// FfiConverter protocol for callback interfaces
fileprivate struct {{ ffi_converter_name }} {
- private static let initCallbackOnce: () = {
- // Swift ensures this initializer code will once run once, even when accessed by multiple threads.
- try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in
- {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)
- }
- }()
-
- private static func ensureCallbackinitialized() {
- _ = initCallbackOnce
- }
-
- static func drop(handle: UniFFICallbackHandle) {
- handleMap.remove(handle: handle)
- }
-
- private static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>()
+ fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>()
}
extension {{ ffi_converter_name }} : FfiConverter {
typealias SwiftType = {{ type_name }}
- // We can use Handle as the FfiType because it's a typealias to UInt64
- typealias FfiType = UniFFICallbackHandle
+ typealias FfiType = UInt64
- public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType {
- ensureCallbackinitialized();
- guard let callback = handleMap.get(handle: handle) else {
- throw UniffiInternalError.unexpectedStaleHandle
- }
- return callback
+ public static func lift(_ handle: UInt64) throws -> SwiftType {
+ try handleMap.get(handle: handle)
}
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
- ensureCallbackinitialized();
- let handle: UniFFICallbackHandle = try readInt(&buf)
+ let handle: UInt64 = try readInt(&buf)
return try lift(handle)
}
- public static func lower(_ v: SwiftType) -> UniFFICallbackHandle {
- ensureCallbackinitialized();
+ public static func lower(_ v: SwiftType) -> UInt64 {
return handleMap.insert(obj: v)
}
public static func write(_ v: SwiftType, into buf: inout [UInt8]) {
- ensureCallbackinitialized();
writeInt(&buf, lower(v))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
index 99f45290cc..1d8b3cf500 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
@@ -1,10 +1,26 @@
// Note that we don't yet support `indirect` for enums.
// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
+{%- call swift::docstring(e, 0) %}
+{% match e.variant_discr_type() %}
+{% when None %}
public enum {{ type_name }} {
{% for variant in e.variants() %}
- case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
+ {%- call swift::docstring(variant, 4) %}
+ case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}(
+ {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %}
+ ){% endif -%}
{% endfor %}
}
+{% when Some with (variant_discr_type) %}
+public enum {{ type_name }} : {{ variant_discr_type|type_name }} {
+ {% for variant in e.variants() %}
+ {%- call swift::docstring(variant, 4) %}
+ case {{ variant.name()|enum_variant_swift_quoted }} = {{ e|variant_discr_literal(loop.index0) }}{% if variant.fields().len() > 0 %}(
+ {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %}
+ ){% endif -%}
+ {% endfor %}
+}
+{% endmatch %}
public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
typealias SwiftType = {{ type_name }}
@@ -15,7 +31,11 @@ public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
{% for variant in e.variants() %}
case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift_quoted }}{% if variant.has_fields() %}(
{%- for field in variant.fields() %}
+ {%- if variant.has_nameless_fields() -%}
+ try {{ field|read_fn }}(from: &buf)
+ {%- else -%}
{{ field.name()|arg_name }}: try {{ field|read_fn }}(from: &buf)
+ {%- endif -%}
{%- if !loop.last %}, {% endif %}
{%- endfor %}
){%- endif %}
@@ -28,10 +48,10 @@ public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
switch value {
{% for variant in e.variants() %}
{% if variant.has_fields() %}
- case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}):
+ case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{%- call swift::field_name(field, loop.index) -%}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}):
writeInt(&buf, Int32({{ loop.index }}))
{% for field in variant.fields() -%}
- {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf)
+ {{ field|write_fn }}({% call swift::field_name(field, loop.index) %}, into: &buf)
{% endfor -%}
{% else %}
case .{{ variant.name()|enum_variant_swift_quoted }}:
@@ -55,5 +75,6 @@ public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuff
}
{% if !contains_object_references %}
+{% if config.experimental_sendable_value_types() %}extension {{ type_name }}: Sendable {} {% endif %}
extension {{ type_name }}: Equatable, Hashable {}
{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
index 786091395b..0702c477e9 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
@@ -1,21 +1,21 @@
+{%- call swift::docstring(e, 0) %}
public enum {{ type_name }} {
{% if e.is_flat() %}
{% for variant in e.variants() %}
- // Simple error enums only carry a message
+ {%- call swift::docstring(variant, 4) %}
case {{ variant.name()|class_name }}(message: String)
{% endfor %}
{%- else %}
{% for variant in e.variants() %}
- case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
+ {%- call swift::docstring(variant, 4) %}
+ case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}(
+ {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %}
+ ){% endif -%}
{% endfor %}
{%- endif %}
-
- fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error {
- return try {{ ffi_converter_name }}.lift(error)
- }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift
deleted file mode 100644
index 167e4c7546..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-private let UNIFFI_RUST_TASK_CALLBACK_SUCCESS: Int8 = 0
-private let UNIFFI_RUST_TASK_CALLBACK_CANCELLED: Int8 = 1
-private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS: Int8 = 0
-private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED: Int8 = 1
-private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR: Int8 = 2
-
-// Encapsulates an executor that can run Rust tasks
-//
-// On Swift, `Task.detached` can handle this we just need to know what priority to send it.
-public struct UniFfiForeignExecutor {
- var priority: TaskPriority
-
- public init(priority: TaskPriority) {
- self.priority = priority
- }
-
- public init() {
- self.priority = Task.currentPriority
- }
-}
-
-fileprivate struct FfiConverterForeignExecutor: FfiConverter {
- typealias SwiftType = UniFfiForeignExecutor
- // Rust uses a pointer to represent the FfiConverterForeignExecutor, but we only need a u8.
- // let's use `Int`, which is equivalent to `size_t`
- typealias FfiType = Int
-
- public static func lift(_ value: FfiType) throws -> SwiftType {
- UniFfiForeignExecutor(priority: TaskPriority(rawValue: numericCast(value)))
- }
- public static func lower(_ value: SwiftType) -> FfiType {
- numericCast(value.priority.rawValue)
- }
-
- public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
- fatalError("FfiConverterForeignExecutor.read not implemented yet")
- }
- public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
- fatalError("FfiConverterForeignExecutor.read not implemented yet")
- }
-}
-
-
-fileprivate func uniffiForeignExecutorCallback(executorHandle: Int, delayMs: UInt32, rustTask: UniFfiRustTaskCallback?, taskData: UnsafeRawPointer?) -> Int8 {
- if let rustTask = rustTask {
- let executor = try! FfiConverterForeignExecutor.lift(executorHandle)
- Task.detached(priority: executor.priority) {
- if delayMs != 0 {
- let nanoseconds: UInt64 = numericCast(delayMs * 1000000)
- try! await Task.sleep(nanoseconds: nanoseconds)
- }
- rustTask(taskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS)
- }
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- } else {
- // When rustTask is null, we should drop the foreign executor.
- // However, since its just a value type, we don't need to do anything here.
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- }
-}
-
-fileprivate func uniffiInitForeignExecutor() {
- {%- match ci.ffi_foreign_executor_callback_set() %}
- {%- when Some with (fn) %}
- {{ fn.name() }}(uniffiForeignExecutorCallback)
- {%- when None %}
- {#- No foreign executor, we don't set anything #}
- {% endmatch %}
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift
new file mode 100644
index 0000000000..6de9f085d6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift
@@ -0,0 +1,40 @@
+fileprivate class UniffiHandleMap<T> {
+ private var map: [UInt64: T] = [:]
+ private let lock = NSLock()
+ private var currentHandle: UInt64 = 1
+
+ func insert(obj: T) -> UInt64 {
+ lock.withLock {
+ let handle = currentHandle
+ currentHandle += 1
+ map[handle] = obj
+ return handle
+ }
+ }
+
+ func get(handle: UInt64) throws -> T {
+ try lock.withLock {
+ guard let obj = map[handle] else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return obj
+ }
+ }
+
+ @discardableResult
+ func remove(handle: UInt64) throws -> T {
+ try lock.withLock {
+ guard let obj = map.removeValue(forKey: handle) else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return obj
+ }
+ }
+
+ var count: Int {
+ get {
+ map.count
+ }
+ }
+}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
index a34b128e23..cfddf7b313 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
@@ -26,9 +26,17 @@ fileprivate enum UniffiInternalError: LocalizedError {
}
}
+fileprivate extension NSLock {
+ func withLock<T>(f: () throws -> T) rethrows -> T {
+ self.lock()
+ defer { self.unlock() }
+ return try f()
+ }
+}
+
fileprivate let CALL_SUCCESS: Int8 = 0
fileprivate let CALL_ERROR: Int8 = 1
-fileprivate let CALL_PANIC: Int8 = 2
+fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2
fileprivate let CALL_CANCELLED: Int8 = 3
fileprivate extension RustCallStatus {
@@ -81,7 +89,7 @@ private func uniffiCheckCallStatus(
throw UniffiInternalError.unexpectedRustCallError
}
- case CALL_PANIC:
+ case CALL_UNEXPECTED_ERROR:
// When the rust code sees a panic, it tries to construct a RustBuffer
// with the message. But if that code panics, then it just sends back
// an empty buffer.
@@ -93,9 +101,39 @@ private func uniffiCheckCallStatus(
}
case CALL_CANCELLED:
- throw CancellationError()
+ fatalError("Cancellation not supported yet")
default:
throw UniffiInternalError.unexpectedRustCallStatusCode
}
}
+
+private func uniffiTraitInterfaceCall<T>(
+ callStatus: UnsafeMutablePointer<RustCallStatus>,
+ makeCall: () throws -> T,
+ writeReturn: (T) -> ()
+) {
+ do {
+ try writeReturn(makeCall())
+ } catch let error {
+ callStatus.pointee.code = CALL_UNEXPECTED_ERROR
+ callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
+ }
+}
+
+private func uniffiTraitInterfaceCallWithError<T, E>(
+ callStatus: UnsafeMutablePointer<RustCallStatus>,
+ makeCall: () throws -> T,
+ writeReturn: (T) -> (),
+ lowerError: (E) -> RustBuffer
+) {
+ do {
+ try writeReturn(makeCall())
+ } catch let error as E {
+ callStatus.pointee.code = CALL_ERROR
+ callStatus.pointee.errorBuf = lowerError(error)
+ } catch {
+ callStatus.pointee.code = CALL_UNEXPECTED_ERROR
+ callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
index 57a77ca6df..0c28bc4c09 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
@@ -1,104 +1,146 @@
{%- let obj = ci|get_object_definition(name) %}
-public protocol {{ obj.name() }}Protocol {
- {% for meth in obj.methods() -%}
- func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) %} {% call swift::throws(meth) -%}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
- {%- else -%}
- {%- endmatch %}
- {% endfor %}
-}
-
-public class {{ type_name }}: {{ obj.name() }}Protocol {
- fileprivate let pointer: UnsafeMutableRawPointer
+{%- let (protocol_name, impl_class_name) = obj|object_names %}
+{%- let methods = obj.methods() %}
+{%- let protocol_docstring = obj.docstring() %}
+
+{%- let is_error = ci.is_name_used_as_error(name) %}
+
+{% include "Protocol.swift" %}
+
+{%- call swift::docstring(obj, 0) %}
+open class {{ impl_class_name }}:
+ {%- for tm in obj.uniffi_traits() %}
+ {%- match tm %}
+ {%- when UniffiTrait::Display { fmt } %}
+ CustomStringConvertible,
+ {%- when UniffiTrait::Debug { fmt } %}
+ CustomDebugStringConvertible,
+ {%- when UniffiTrait::Eq { eq, ne } %}
+ Equatable,
+ {%- when UniffiTrait::Hash { hash } %}
+ Hashable,
+ {%- else %}
+ {%- endmatch %}
+ {%- endfor %}
+ {%- if is_error %}
+ Error,
+ {% endif %}
+ {{ protocol_name }} {
+ fileprivate let pointer: UnsafeMutableRawPointer!
+
+ /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly.
+ public struct NoPointer {
+ public init() {}
+ }
// TODO: We'd like this to be `private` but for Swifty reasons,
// we can't implement `FfiConverter` without making this `required` and we can't
// make it `required` without making it `public`.
- required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
+ required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
self.pointer = pointer
}
+ /// This constructor can be used to instantiate a fake object.
+ /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject].
+ ///
+ /// - Warning:
+ /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash.
+ public init(noPointer: NoPointer) {
+ self.pointer = nil
+ }
+
+ public func uniffiClonePointer() -> UnsafeMutableRawPointer {
+ return try! rustCall { {{ obj.ffi_object_clone().name() }}(self.pointer, $0) }
+ }
+
{%- match obj.primary_constructor() %}
{%- when Some with (cons) %}
- public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} {
- self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %})
- }
+ {%- call swift::ctor_decl(cons, 4) %}
{%- when None %}
+ // No primary constructor declared for this class.
{%- endmatch %}
deinit {
+ guard let pointer = pointer else {
+ return
+ }
+
try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) }
}
{% for cons in obj.alternate_constructors() %}
-
- public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ type_name }} {
- return {{ type_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %})
- }
-
+ {%- call swift::func_decl("public static func", cons, 4) %}
{% endfor %}
- {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #}
{% for meth in obj.methods() -%}
- {%- if meth.is_async() %}
-
- public func {{ meth.name()|fn_name }}({%- call swift::arg_list_decl(meth) -%}) async {% call swift::throws(meth) %}{% match meth.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} {
- return {% call swift::try(meth) %} await uniffiRustCallAsync(
- rustFutureFunc: {
- {{ meth.ffi_func().name() }}(
- self.pointer
- {%- for arg in meth.arguments() -%}
- ,
- {{ arg|lower_fn }}({{ arg.name()|var_name }})
- {%- endfor %}
- )
- },
- pollFunc: {{ meth.ffi_rust_future_poll(ci) }},
- completeFunc: {{ meth.ffi_rust_future_complete(ci) }},
- freeFunc: {{ meth.ffi_rust_future_free(ci) }},
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- liftFunc: {{ return_type|lift_fn }},
- {%- when None %}
- liftFunc: { $0 },
- {%- endmatch %}
- {%- match meth.throws_type() %}
- {%- when Some with (e) %}
- errorHandler: {{ e|ffi_converter_name }}.lift
- {%- else %}
- errorHandler: nil
- {% endmatch %}
+ {%- call swift::func_decl("open func", meth, 4) %}
+ {% endfor %}
+
+ {%- for tm in obj.uniffi_traits() %}
+ {%- match tm %}
+ {%- when UniffiTrait::Display { fmt } %}
+ open var description: String {
+ return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}(
+ {% call swift::to_ffi_call(fmt) %}
)
}
-
- {% else -%}
-
- {%- match meth.return_type() -%}
-
- {%- when Some with (return_type) %}
-
- public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} {
- return {% call swift::try(meth) %} {{ return_type|lift_fn }}(
- {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %}
+ {%- when UniffiTrait::Debug { fmt } %}
+ open var debugDescription: String {
+ return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}(
+ {% call swift::to_ffi_call(fmt) %}
)
}
-
- {%- when None %}
-
- public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} {
- {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %}
+ {%- when UniffiTrait::Eq { eq, ne } %}
+ public static func == (self: {{ impl_class_name }}, other: {{ impl_class_name }}) -> Bool {
+ return {% call swift::try(eq) %} {{ eq.return_type().unwrap()|lift_fn }}(
+ {% call swift::to_ffi_call(eq) %}
+ )
+ }
+ {%- when UniffiTrait::Hash { hash } %}
+ open func hash(into hasher: inout Hasher) {
+ let val = {% call swift::try(hash) %} {{ hash.return_type().unwrap()|lift_fn }}(
+ {% call swift::to_ffi_call(hash) %}
+ )
+ hasher.combine(val)
}
+ {%- else %}
+ {%- endmatch %}
+ {%- endfor %}
- {%- endmatch -%}
- {%- endif -%}
- {% endfor %}
}
+{%- if obj.has_callback_interface() %}
+{%- let callback_handler = format!("uniffiCallbackInterface{}", name) %}
+{%- let callback_init = format!("uniffiCallbackInit{}", name) %}
+{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %}
+{%- let vtable_methods = obj.vtable_methods() %}
+{%- let ffi_init_callback = obj.ffi_init_callback() %}
+{% include "CallbackInterfaceImpl.swift" %}
+{%- endif %}
+
public struct {{ ffi_converter_name }}: FfiConverter {
+ {%- if obj.has_callback_interface() %}
+ fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>()
+ {%- endif %}
+
typealias FfiType = UnsafeMutableRawPointer
typealias SwiftType = {{ type_name }}
+ public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} {
+ return {{ impl_class_name }}(unsafeFromRawPointer: pointer)
+ }
+
+ public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer {
+ {%- if obj.has_callback_interface() %}
+ guard let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: handleMap.insert(obj: value))) else {
+ fatalError("Cast to UnsafeMutableRawPointer failed")
+ }
+ return ptr
+ {%- else %}
+ return value.uniffiClonePointer()
+ {%- endif %}
+ }
+
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
let v: UInt64 = try readInt(&buf)
// The Rust code won't compile if a pointer won't fit in a UInt64.
@@ -115,15 +157,30 @@ public struct {{ ffi_converter_name }}: FfiConverter {
// The Rust code won't compile if a pointer won't fit in a `UInt64`.
writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value)))))
}
+}
- public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} {
- return {{ type_name}}(unsafeFromRawPointer: pointer)
+{# Objects as error #}
+{%- if is_error %}
+{# Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer holding a pointer #}
+public struct {{ ffi_converter_name }}__as_error: FfiConverterRustBuffer {
+ public static func lift(_ buf: RustBuffer) throws -> {{ type_name }} {
+ var reader = createReader(data: Data(rustBuffer: buf))
+ return try {{ ffi_converter_name }}.read(from: &reader)
}
- public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer {
- return value.pointer
+ public static func lower(_ value: {{ type_name }}) -> RustBuffer {
+ fatalError("not implemented")
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
+ fatalError("not implemented")
+ }
+
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ fatalError("not implemented")
}
}
+{%- endif %}
{#
We always write these public functions just in case the enum is used as
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift
new file mode 100644
index 0000000000..7df953558a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift
@@ -0,0 +1,12 @@
+{%- call swift::docstring_value(protocol_docstring, 0) %}
+public protocol {{ protocol_name }} : AnyObject {
+ {% for meth in methods.iter() -%}
+ {%- call swift::docstring(meth, 4) %}
+ func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) -%}{% call swift::throws(meth) -%}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
index 44de9dd358..c262a7a216 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
@@ -1,12 +1,14 @@
{%- let rec = ci|get_record_definition(name) %}
+{%- call swift::docstring(rec, 0) %}
public struct {{ type_name }} {
{%- for field in rec.fields() %}
- public var {{ field.name()|var_name }}: {{ field|type_name }}
+ {%- call swift::docstring(field, 4) %}
+ public {% if config.generate_immutable_records() %}let{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name }}
{%- endfor %}
// Default memberwise initializers are never public by default, so we
// declare one manually.
- public init({% call swift::field_list_decl(rec) %}) {
+ public init({% call swift::field_list_decl(rec, false) %}) {
{%- for field in rec.fields() %}
self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
{%- endfor %}
@@ -14,6 +16,7 @@ public struct {{ type_name }} {
}
{% if !contains_object_references %}
+{% if config.experimental_sendable_value_types() %}extension {{ type_name }}: Sendable {} {% endif %}
extension {{ type_name }}: Equatable, Hashable {
public static func ==(lhs: {{ type_name }}, rhs: {{ type_name }}) -> Bool {
{%- for field in rec.fields() %}
@@ -34,12 +37,16 @@ extension {{ type_name }}: Equatable, Hashable {
public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
- return try {{ type_name }}(
+ return {%- if rec.has_fields() %}
+ try {{ type_name }}(
{%- for field in rec.fields() %}
- {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf)
- {%- if !loop.last %}, {% endif %}
+ {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf)
+ {%- if !loop.last %}, {% endif %}
{%- endfor %}
)
+ {%- else %}
+ {{ type_name }}()
+ {%- endif %}
}
public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
index 2f737b6635..a053334a30 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
@@ -7,6 +7,10 @@ fileprivate extension RustBuffer {
self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data)
}
+ static func empty() -> RustBuffer {
+ RustBuffer(capacity: 0, len:0, data: nil)
+ }
+
static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer {
try! rustCall { {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), $0) }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
index a2c6311931..ce946076f7 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
@@ -1,48 +1 @@
-{%- if func.is_async() %}
-
-public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) async {% call swift::throws(func) %}{% match func.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} {
- return {% call swift::try(func) %} await uniffiRustCallAsync(
- rustFutureFunc: {
- {{ func.ffi_func().name() }}(
- {%- for arg in func.arguments() %}
- {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %}
- {%- endfor %}
- )
- },
- pollFunc: {{ func.ffi_rust_future_poll(ci) }},
- completeFunc: {{ func.ffi_rust_future_complete(ci) }},
- freeFunc: {{ func.ffi_rust_future_free(ci) }},
- {%- match func.return_type() %}
- {%- when Some(return_type) %}
- liftFunc: {{ return_type|lift_fn }},
- {%- when None %}
- liftFunc: { $0 },
- {%- endmatch %}
- {%- match func.throws_type() %}
- {%- when Some with (e) %}
- errorHandler: {{ e|ffi_converter_name }}.lift
- {%- else %}
- errorHandler: nil
- {% endmatch %}
- )
-}
-
-{% else %}
-
-{%- match func.return_type() -%}
-{%- when Some with (return_type) %}
-
-public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) {% call swift::throws(func) %} -> {{ return_type|type_name }} {
- return {% call swift::try(func) %} {{ return_type|lift_fn }}(
- {% call swift::to_ffi_call(func) %}
- )
-}
-
-{%- when None %}
-
-public func {{ func.name()|fn_name }}({% call swift::arg_list_decl(func) %}) {% call swift::throws(func) %} {
- {% call swift::to_ffi_call(func) %}
-}
-
-{% endmatch %}
-{%- endif %}
+{%- call swift::func_decl("public func", func, 0) %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
index aba34f4b0b..5e26758f3c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
@@ -64,9 +64,6 @@
{%- when Type::CallbackInterface { name, module_path } %}
{%- include "CallbackInterfaceTemplate.swift" %}
-{%- when Type::ForeignExecutor %}
-{%- include "ForeignExecutorTemplate.swift" %}
-
{%- when Type::Custom { name, module_path, builtin } %}
{%- include "CustomType.swift" %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
index 0a125e6f61..8692cd6ff0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
@@ -8,28 +8,97 @@
{%- call try(func) -%}
{%- match func.throws_type() -%}
{%- when Some with (e) -%}
- rustCallWithError({{ e|ffi_converter_name }}.lift) {
+ rustCallWithError({{ e|ffi_error_converter_name }}.lift) {
{%- else -%}
rustCall() {
{%- endmatch %}
- {{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} $0)
+ {{ func.ffi_func().name() }}(
+ {%- if func.takes_self() %}self.uniffiClonePointer(),{% endif %}
+ {%- call arg_list_lowered(func) -%} $0
+ )
}
{%- endmacro -%}
-{%- macro to_ffi_call_with_prefix(prefix, func) -%}
-{% call try(func) %}
- {%- match func.throws_type() %}
- {%- when Some with (e) %}
- rustCallWithError({{ e|ffi_converter_name }}.lift) {
+// eg, `public func foo_bar() { body }`
+{%- macro func_decl(func_decl, callable, indent) %}
+{%- call docstring(callable, indent) %}
+{{ func_decl }} {{ callable.name()|fn_name }}(
+ {%- call arg_list_decl(callable) -%})
+ {%- call async(callable) %}
+ {%- call throws(callable) %}
+ {%- match callable.return_type() %}
+ {%- when Some with (return_type) %} -> {{ return_type|type_name }}
+ {%- when None %}
+ {%- endmatch %} {
+ {%- call call_body(callable) %}
+}
+{%- endmacro %}
+
+// primary ctor - no name, no return-type.
+{%- macro ctor_decl(callable, indent) %}
+{%- call docstring(callable, indent) %}
+public convenience init(
+ {%- call arg_list_decl(callable) -%}) {%- call async(callable) %} {%- call throws(callable) %} {
+ {%- if callable.is_async() %}
+ let pointer =
+ {%- call call_async(callable) %}
+ {# The async mechanism returns an already constructed self.
+ We work around that by cloning the pointer from that object, then
+ assune the old object dies as there are no other references possible.
+ #}
+ .uniffiClonePointer()
{%- else %}
- rustCall() {
- {% endmatch %}
- {{ func.ffi_func().name() }}(
- {{- prefix }}, {% call arg_list_lowered(func) -%} $0
- )
+ let pointer =
+ {% call to_ffi_call(callable) %}
+ {%- endif %}
+ self.init(unsafeFromRawPointer: pointer)
}
{%- endmacro %}
+{%- macro call_body(callable) %}
+{%- if callable.is_async() %}
+ return {%- call call_async(callable) %}
+{%- else %}
+{%- match callable.return_type() -%}
+{%- when Some with (return_type) %}
+ return {% call try(callable) %} {{ return_type|lift_fn }}({% call to_ffi_call(callable) %})
+{%- when None %}
+{%- call to_ffi_call(callable) %}
+{%- endmatch %}
+{%- endif %}
+
+{%- endmacro %}
+
+{%- macro call_async(callable) %}
+ {% call try(callable) %} await uniffiRustCallAsync(
+ rustFutureFunc: {
+ {{ callable.ffi_func().name() }}(
+ {%- if callable.takes_self() %}
+ self.uniffiClonePointer(){% if !callable.arguments().is_empty() %},{% endif %}
+ {% endif %}
+ {%- for arg in callable.arguments() -%}
+ {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %}
+ {%- endfor %}
+ )
+ },
+ pollFunc: {{ callable.ffi_rust_future_poll(ci) }},
+ completeFunc: {{ callable.ffi_rust_future_complete(ci) }},
+ freeFunc: {{ callable.ffi_rust_future_free(ci) }},
+ {%- match callable.return_type() %}
+ {%- when Some(return_type) %}
+ liftFunc: {{ return_type|lift_fn }},
+ {%- when None %}
+ liftFunc: { $0 },
+ {%- endmatch %}
+ {%- match callable.throws_type() %}
+ {%- when Some with (e) %}
+ errorHandler: {{ e|ffi_error_converter_name }}.lift
+ {%- else %}
+ errorHandler: nil
+ {% endmatch %}
+ )
+{%- endmacro %}
+
{%- macro arg_list_lowered(func) %}
{%- for arg in func.arguments() %}
{{ arg|lower_fn }}({{ arg.name()|var_name }}),
@@ -56,17 +125,30 @@
// Field lists as used in Swift declarations of Records and Enums.
// Note the var_name and type_name filters.
-#}
-{% macro field_list_decl(item) %}
+{% macro field_list_decl(item, has_nameless_fields) %}
{%- for field in item.fields() -%}
+ {%- call docstring(field, 8) %}
+ {%- if has_nameless_fields %}
+ {{- field|type_name -}}
+ {%- if !loop.last -%}, {%- endif -%}
+ {%- else -%}
{{ field.name()|var_name }}: {{ field|type_name -}}
{%- match field.default_value() %}
{%- when Some with(literal) %} = {{ literal|literal_swift(field) }}
{%- else %}
{%- endmatch -%}
{% if !loop.last %}, {% endif %}
+ {%- endif -%}
{%- endfor %}
{%- endmacro %}
+{% macro field_name(field, field_num) %}
+{%- if field.name().is_empty() -%}
+v{{- field_num -}}
+{%- else -%}
+{{ field.name()|var_name }}
+{%- endif -%}
+{%- endmacro %}
{% macro arg_list_protocol(func) %}
{%- for arg in func.arguments() -%}
@@ -75,15 +157,26 @@
{%- endfor %}
{%- endmacro %}
-
{%- macro async(func) %}
-{%- if func.is_async() %}async{% endif %}
+{%- if func.is_async() %}async {% endif %}
{%- endmacro -%}
{%- macro throws(func) %}
-{%- if func.throws() %}throws{% endif %}
+{%- if func.throws() %}throws {% endif %}
{%- endmacro -%}
{%- macro try(func) %}
{%- if func.throws() %}try {% else %}try! {% endif %}
{%- endmacro -%}
+
+{%- macro docstring_value(maybe_docstring, indent_spaces) %}
+{%- match maybe_docstring %}
+{%- when Some(docstring) %}
+{{ docstring|docstring(indent_spaces) }}
+{%- else %}
+{%- endmatch %}
+{%- endmacro %}
+
+{%- macro docstring(defn, indent_spaces) %}
+{%- call docstring_value(defn.docstring(), indent_spaces) %}
+{%- endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
index c34d348efb..17fdde74e0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
@@ -1,5 +1,10 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
+
+// swiftlint:disable all
+
+{%- call swift::docstring_value(ci.namespace_docstring(), 0) %}
+
{%- import "macros.swift" as swift %}
import Foundation
{%- for imported_class in self.imports() %}
@@ -15,6 +20,7 @@ import {{ config.ffi_module_name() }}
{% include "RustBufferTemplate.swift" %}
{% include "Helpers.swift" %}
+{% include "HandleMap.swift" %}
// Public interface members begin here.
{{ type_helper_code }}
@@ -66,3 +72,5 @@ private func uniffiEnsureInitialized() {
fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
}
+
+// swiftlint:enable all
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
index c3b2f15277..195a77696b 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
@@ -2,10 +2,7 @@
License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-use crate::{
- bindings::{RunScriptOptions, TargetLanguage},
- library_mode::generate_bindings,
-};
+use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault};
use anyhow::{bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
@@ -15,6 +12,8 @@ use std::io::Write;
use std::process::{Command, Stdio};
use uniffi_testing::UniFFITestHelper;
+use crate::bindings::TargetLanguage;
+
/// Run Swift tests for a UniFFI test fixture
pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
run_script(
@@ -36,7 +35,7 @@ pub fn run_script(
args: Vec<String>,
options: &RunScriptOptions,
) -> Result<()> {
- let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let script_path = Utf8Path::new(script_file).canonicalize_utf8()?;
let test_helper = UniFFITestHelper::new(crate_name)?;
let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
@@ -126,8 +125,17 @@ struct GeneratedSources {
impl GeneratedSources {
fn new(crate_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result<Self> {
- let sources =
- generate_bindings(cdylib_path, None, &[TargetLanguage::Swift], out_dir, false)?;
+ let sources = generate_bindings(
+ cdylib_path,
+ None,
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Swift],
+ try_format_code: false,
+ },
+ None,
+ out_dir,
+ false,
+ )?;
let main_source = sources
.iter()
.find(|s| s.package.name == crate_name)
@@ -169,7 +177,7 @@ fn create_command(program: &str, options: &RunScriptOptions) -> Command {
if !options.show_compiler_messages {
// This prevents most compiler messages, but not remarks
command.arg("-suppress-warnings");
- // This gets the remarks. Note: swift will eventually get a `-supress-remarks` argument,
+ // This gets the remarks. Note: swift will eventually get a `-suppress-remarks` argument,
// maybe we can eventually move to that
command.stderr(Stdio::null());
}
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_)
diff --git a/third_party/rust/uniffi_bindgen/src/lib.rs b/third_party/rust/uniffi_bindgen/src/lib.rs
index 019b24022f..dfc90b32a6 100644
--- a/third_party/rust/uniffi_bindgen/src/lib.rs
+++ b/third_party/rust/uniffi_bindgen/src/lib.rs
@@ -58,9 +58,8 @@
//!
//! ### 3) Generate and include component scaffolding from the UDL file
//!
-//! First you will need to install `uniffi-bindgen` on your system using `cargo install uniffi_bindgen`.
-//! Then add to your crate `uniffi_build` under `[build-dependencies]`.
-//! Finally, add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding`
+//! Add to your crate `uniffi_build` under `[build-dependencies]`,
+//! then add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding`
//! to process your `.udl` file. This will generate some Rust code to be included in the top-level source
//! code of your crate. If your UDL file is named `example.udl`, then your build script would call:
//!
@@ -77,12 +76,13 @@
//!
//! ### 4) Generate foreign language bindings for the library
//!
-//! The `uniffi-bindgen` utility provides a command-line tool that can produce code to
+//! You will need ensure a local `uniffi-bindgen` - see <https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html>
+//! This utility provides a command-line tool that can produce code to
//! consume the Rust library in any of several supported languages.
//! It is done by calling (in kotlin for example):
//!
//! ```text
-//! uniffi-bindgen --language kotlin ./src/example.udl
+//! cargo run --bin -p uniffi-bindgen --language kotlin ./src/example.udl
//! ```
//!
//! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings
@@ -160,15 +160,16 @@ pub trait BindingGenerator: Sized {
ci: &ComponentInterface,
config: &Self::Config,
out_dir: &Utf8Path,
+ try_format_code: bool,
) -> Result<()>;
/// Check if `library_path` used by library mode is valid for this generator
fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()>;
}
-struct BindingGeneratorDefault {
- target_languages: Vec<TargetLanguage>,
- try_format_code: bool,
+pub struct BindingGeneratorDefault {
+ pub target_languages: Vec<TargetLanguage>,
+ pub try_format_code: bool,
}
impl BindingGenerator for BindingGeneratorDefault {
@@ -179,6 +180,7 @@ impl BindingGenerator for BindingGeneratorDefault {
ci: &ComponentInterface,
config: &Self::Config,
out_dir: &Utf8Path,
+ _try_format_code: bool,
) -> Result<()> {
for &language in &self.target_languages {
bindings::write_bindings(
@@ -219,12 +221,13 @@ impl BindingGenerator for BindingGeneratorDefault {
/// - `library_file`: The path to a dynamic library to attempt to extract the definitions from and extend the component interface with. No extensions to component interface occur if it's [`None`]
/// - `crate_name`: Override the default crate name that is guessed from UDL file path.
pub fn generate_external_bindings<T: BindingGenerator>(
- binding_generator: T,
+ binding_generator: &T,
udl_file: impl AsRef<Utf8Path>,
config_file_override: Option<impl AsRef<Utf8Path>>,
out_dir_override: Option<impl AsRef<Utf8Path>>,
library_file: Option<impl AsRef<Utf8Path>>,
crate_name: Option<&str>,
+ try_format_code: bool,
) -> Result<()> {
let crate_name = crate_name
.map(|c| Ok(c.to_string()))
@@ -253,7 +256,7 @@ pub fn generate_external_bindings<T: BindingGenerator>(
udl_file.as_ref(),
out_dir_override.as_ref().map(|p| p.as_ref()),
)?;
- binding_generator.write_bindings(&component, &config, &out_dir)
+ binding_generator.write_bindings(&component, &config, &out_dir, try_format_code)
}
// Generate the infrastructural Rust code for implementing the UDL interface,
@@ -301,25 +304,23 @@ fn generate_component_scaffolding_inner(
// Generate the bindings in the target languages that call the scaffolding
// Rust code.
-pub fn generate_bindings(
+pub fn generate_bindings<T: BindingGenerator>(
udl_file: &Utf8Path,
config_file_override: Option<&Utf8Path>,
- target_languages: Vec<TargetLanguage>,
+ binding_generator: T,
out_dir_override: Option<&Utf8Path>,
library_file: Option<&Utf8Path>,
crate_name: Option<&str>,
try_format_code: bool,
) -> Result<()> {
generate_external_bindings(
- BindingGeneratorDefault {
- target_languages,
- try_format_code,
- },
+ &binding_generator,
udl_file,
config_file_override,
out_dir_override,
library_file,
crate_name,
+ try_format_code,
)
}
@@ -417,22 +418,53 @@ fn format_code_with_rustfmt(path: &Utf8Path) -> Result<()> {
Ok(())
}
+/// Load TOML from file if the file exists.
+fn load_toml_file(source: Option<&Utf8Path>) -> Result<Option<toml::value::Table>> {
+ if let Some(source) = source {
+ if source.exists() {
+ let contents =
+ fs::read_to_string(source).with_context(|| format!("read file: {:?}", source))?;
+ return Ok(Some(
+ toml::de::from_str(&contents)
+ .with_context(|| format!("parse toml: {:?}", source))?,
+ ));
+ }
+ }
+
+ Ok(None)
+}
+
+/// Load the default `uniffi.toml` config, merge TOML trees with `config_file_override` if specified.
fn load_initial_config<Config: DeserializeOwned>(
crate_root: &Utf8Path,
config_file_override: Option<&Utf8Path>,
) -> Result<Config> {
- let path = match config_file_override {
- Some(cfg) => Some(cfg.to_owned()),
- None => crate_root.join("uniffi.toml").canonicalize_utf8().ok(),
- };
- let toml_config = match path {
- Some(path) => {
- let contents = fs::read_to_string(path).context("Failed to read config file")?;
- toml::de::from_str(&contents)?
+ let mut config = load_toml_file(Some(crate_root.join("uniffi.toml").as_path()))
+ .context("default config")?
+ .unwrap_or(toml::value::Table::default());
+
+ let override_config = load_toml_file(config_file_override).context("override config")?;
+ if let Some(override_config) = override_config {
+ merge_toml(&mut config, override_config);
+ }
+
+ Ok(toml::Value::from(config).try_into()?)
+}
+
+fn merge_toml(a: &mut toml::value::Table, b: toml::value::Table) {
+ for (key, value) in b.into_iter() {
+ match a.get_mut(&key) {
+ Some(existing_value) => match (existing_value, value) {
+ (toml::Value::Table(ref mut t0), toml::Value::Table(t1)) => {
+ merge_toml(t0, t1);
+ }
+ (v, value) => *v = value,
+ },
+ None => {
+ a.insert(key, value);
+ }
}
- None => toml::Value::from(toml::value::Table::default()),
- };
- Ok(toml_config.try_into()?)
+ }
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@@ -516,4 +548,56 @@ mod test {
let not_a_crate_root = &this_crate_root.join("src/templates");
assert!(guess_crate_root(&not_a_crate_root.join("src/example.udl")).is_err());
}
+
+ #[test]
+ fn test_merge_toml() {
+ let default = r#"
+ foo = "foo"
+ bar = "bar"
+
+ [table1]
+ foo = "foo"
+ bar = "bar"
+ "#;
+ let mut default = toml::de::from_str(default).unwrap();
+
+ let override_toml = r#"
+ # update key
+ bar = "BAR"
+ # insert new key
+ baz = "BAZ"
+
+ [table1]
+ # update key
+ bar = "BAR"
+ # insert new key
+ baz = "BAZ"
+
+ # new table
+ [table1.table2]
+ bar = "BAR"
+ baz = "BAZ"
+ "#;
+ let override_toml = toml::de::from_str(override_toml).unwrap();
+
+ let expected = r#"
+ foo = "foo"
+ bar = "BAR"
+ baz = "BAZ"
+
+ [table1]
+ foo = "foo"
+ bar = "BAR"
+ baz = "BAZ"
+
+ [table1.table2]
+ bar = "BAR"
+ baz = "BAZ"
+ "#;
+ let expected: toml::value::Table = toml::de::from_str(expected).unwrap();
+
+ merge_toml(&mut default, override_toml);
+
+ assert_eq!(&expected, &default);
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/library_mode.rs b/third_party/rust/uniffi_bindgen/src/library_mode.rs
index f170ea5e91..c460c03d9f 100644
--- a/third_party/rust/uniffi_bindgen/src/library_mode.rs
+++ b/third_party/rust/uniffi_bindgen/src/library_mode.rs
@@ -16,8 +16,8 @@
/// - UniFFI can figure out the package/module names for each crate, eliminating the external
/// package maps.
use crate::{
- bindings::TargetLanguage, load_initial_config, macro_metadata, BindingGenerator,
- BindingGeneratorDefault, BindingsConfig, ComponentInterface, Result,
+ load_initial_config, macro_metadata, BindingGenerator, BindingsConfig, ComponentInterface,
+ Result,
};
use anyhow::{bail, Context};
use camino::Utf8Path;
@@ -33,21 +33,21 @@ use uniffi_meta::{
/// Generate foreign bindings
///
/// Returns the list of sources used to generate the bindings, in no particular order.
-pub fn generate_bindings(
+pub fn generate_bindings<T: BindingGenerator + ?Sized>(
library_path: &Utf8Path,
crate_name: Option<String>,
- target_languages: &[TargetLanguage],
+ binding_generator: &T,
+ config_file_override: Option<&Utf8Path>,
out_dir: &Utf8Path,
try_format_code: bool,
-) -> Result<Vec<Source<crate::Config>>> {
+) -> Result<Vec<Source<T::Config>>> {
generate_external_bindings(
- BindingGeneratorDefault {
- target_languages: target_languages.into(),
- try_format_code,
- },
+ binding_generator,
library_path,
- crate_name,
+ crate_name.clone(),
+ config_file_override,
out_dir,
+ try_format_code,
)
}
@@ -55,10 +55,12 @@ pub fn generate_bindings(
///
/// Returns the list of sources used to generate the bindings, in no particular order.
pub fn generate_external_bindings<T: BindingGenerator>(
- binding_generator: T,
+ binding_generator: &T,
library_path: &Utf8Path,
crate_name: Option<String>,
+ config_file_override: Option<&Utf8Path>,
out_dir: &Utf8Path,
+ try_format_code: bool,
) -> Result<Vec<Source<T::Config>>> {
let cargo_metadata = MetadataCommand::new()
.exec()
@@ -66,7 +68,12 @@ pub fn generate_external_bindings<T: BindingGenerator>(
let cdylib_name = calc_cdylib_name(library_path);
binding_generator.check_library_path(library_path, cdylib_name)?;
- let mut sources = find_sources(&cargo_metadata, library_path, cdylib_name)?;
+ let mut sources = find_sources(
+ &cargo_metadata,
+ library_path,
+ cdylib_name,
+ config_file_override,
+ )?;
for i in 0..sources.len() {
// Partition up the sources list because we're eventually going to call
// `update_from_dependency_configs()` which requires an exclusive reference to one source and
@@ -101,7 +108,7 @@ pub fn generate_external_bindings<T: BindingGenerator>(
}
for source in sources.iter() {
- binding_generator.write_bindings(&source.ci, &source.config, out_dir)?;
+ binding_generator.write_bindings(&source.ci, &source.config, out_dir, try_format_code)?;
}
Ok(sources)
@@ -118,10 +125,10 @@ pub struct Source<Config: BindingsConfig> {
// If `library_path` is a C dynamic library, return its name
pub fn calc_cdylib_name(library_path: &Utf8Path) -> Option<&str> {
- let cdylib_extentions = [".so", ".dll", ".dylib"];
+ let cdylib_extensions = [".so", ".dll", ".dylib"];
let filename = library_path.file_name()?;
let filename = filename.strip_prefix("lib").unwrap_or(filename);
- for ext in cdylib_extentions {
+ for ext in cdylib_extensions {
if let Some(f) = filename.strip_suffix(ext) {
return Some(f);
}
@@ -133,6 +140,7 @@ fn find_sources<Config: BindingsConfig>(
cargo_metadata: &cargo_metadata::Metadata,
library_path: &Utf8Path,
cdylib_name: Option<&str>,
+ config_file_override: Option<&Utf8Path>,
) -> Result<Vec<Source<Config>>> {
let items = macro_metadata::extract_from_library(library_path)?;
let mut metadata_groups = create_metadata_groups(&items);
@@ -178,7 +186,7 @@ fn find_sources<Config: BindingsConfig>(
ci.add_metadata(metadata)?;
};
ci.add_metadata(group)?;
- let mut config = load_initial_config::<Config>(crate_root, None)?;
+ let mut config = load_initial_config::<Config>(crate_root, config_file_override)?;
if let Some(cdylib_name) = cdylib_name {
config.update_from_cdylib_name(cdylib_name);
}
diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs
index 7ce6c3a70b..69fad1980e 100644
--- a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs
+++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs
@@ -4,9 +4,7 @@
use crate::interface::{CallbackInterface, ComponentInterface, Enum, Record, Type};
use anyhow::{bail, Context};
-use uniffi_meta::{
- create_metadata_groups, group_metadata, EnumMetadata, ErrorMetadata, Metadata, MetadataGroup,
-};
+use uniffi_meta::{create_metadata_groups, group_metadata, EnumMetadata, Metadata, MetadataGroup};
/// Add Metadata items to the ComponentInterface
///
@@ -98,7 +96,9 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res
iface.add_record_definition(record)?;
}
Metadata::Enum(meta) => {
- let flat = meta.variants.iter().all(|v| v.fields.is_empty());
+ let flat = meta
+ .forced_flatness
+ .unwrap_or_else(|| meta.variants.iter().all(|v| v.fields.is_empty()));
add_enum_to_ci(iface, meta, flat)?;
}
Metadata::Object(meta) => {
@@ -117,22 +117,11 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res
module_path: meta.module_path.clone(),
name: meta.name.clone(),
})?;
- iface.add_callback_interface_definition(CallbackInterface::new(
- meta.name,
- meta.module_path,
- ));
+ iface.add_callback_interface_definition(CallbackInterface::try_from(meta)?);
}
Metadata::TraitMethod(meta) => {
iface.add_trait_method_meta(meta)?;
}
- Metadata::Error(meta) => {
- iface.note_name_used_as_error(meta.name());
- match meta {
- ErrorMetadata::Enum { enum_, is_flat } => {
- add_enum_to_ci(iface, enum_, is_flat)?;
- }
- };
- }
Metadata::CustomType(meta) => {
iface.types.add_known_type(&Type::Custom {
module_path: meta.module_path.clone(),
diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs
index 25b5ef17ba..6d440919f1 100644
--- a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs
+++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs
@@ -30,7 +30,7 @@ fn extract_from_bytes(file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> {
Object::PE(pe) => extract_from_pe(pe, file_data),
Object::Mach(mach) => extract_from_mach(mach, file_data),
Object::Archive(archive) => extract_from_archive(archive, file_data),
- Object::Unknown(_) => bail!("Unknown library format"),
+ _ => bail!("Unknown library format"),
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs
index f3759cf6fa..7fd81831aa 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs
@@ -45,7 +45,6 @@ mod filters {
format!("std::sync::Arc<{}>", imp.rust_name_for(name))
}
Type::CallbackInterface { name, .. } => format!("Box<dyn r#{name}>"),
- Type::ForeignExecutor => "::uniffi::ForeignExecutor".into(),
Type::Optional { inner_type } => {
format!("std::option::Option<{}>", type_rs(inner_type)?)
}
@@ -64,41 +63,12 @@ mod filters {
kind: ExternalKind::Interface,
..
} => format!("::std::sync::Arc<r#{name}>"),
- Type::External { name, .. } => format!("r#{name}"),
- })
- }
-
- // Map a type to Rust code that specifies the FfiConverter implementation.
- //
- // This outputs something like `<MyStruct as Lift<crate::UniFfiTag>>`
- pub fn ffi_trait(type_: &Type, trait_name: &str) -> Result<String, askama::Error> {
- Ok(match type_ {
Type::External {
name,
- kind: ExternalKind::Interface,
+ kind: ExternalKind::Trait,
..
- } => {
- format!("<::std::sync::Arc<r#{name}> as ::uniffi::{trait_name}<crate::UniFfiTag>>")
- }
- _ => format!(
- "<{} as ::uniffi::{trait_name}<crate::UniFfiTag>>",
- type_rs(type_)?
- ),
- })
- }
-
- pub fn return_type<T: Callable>(callable: &T) -> Result<String, askama::Error> {
- let return_type = match callable.return_type() {
- Some(t) => type_rs(&t)?,
- None => "()".to_string(),
- };
- match callable.throws_type() {
- Some(t) => type_rs(&t)?,
- None => "()".to_string(),
- };
- Ok(match callable.throws_type() {
- Some(e) => format!("::std::result::Result<{return_type}, {}>", type_rs(&e)?),
- None => return_type,
+ } => format!("::std::sync::Arc<dyn r#{name}>"),
+ Type::External { name, .. } => format!("r#{name}"),
})
}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs
index 64c69e4d8e..658f4c8de5 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs
@@ -1,82 +1,17 @@
-{#
-// For each Callback Interface definition, we assume that there is a corresponding trait defined in Rust client code.
-// If the UDL callback interface and Rust trait's methods don't match, the Rust compiler will complain.
-// We generate:
-// * an init function to accept that `ForeignCallback` from the foreign language, and stores it.
-// * a holder for a `ForeignCallback`, of type `uniffi::ForeignCallbackInternals`.
-// * a proxy `struct` which implements the `trait` that the Callback Interface corresponds to. This
-// is the object that client code interacts with.
-// - for each method, arguments will be packed into a `RustBuffer` and sent over the `ForeignCallback` to be
-// unpacked and called. The return value is packed into another `RustBuffer` and sent back to Rust.
-// - a `Drop` `impl`, which tells the foreign language to forget about the real callback object.
-#}
-{% let trait_name = cbi.name() -%}
-{% let trait_impl = format!("UniFFICallbackHandler{}", trait_name) %}
-{% let foreign_callback_internals = format!("foreign_callback_{}_internals", trait_name)|upper -%}
-
-// Register a foreign callback for getting across the FFI.
-#[doc(hidden)]
-static {{ foreign_callback_internals }}: uniffi::ForeignCallbackInternals = uniffi::ForeignCallbackInternals::new();
-
-#[doc(hidden)]
-#[no_mangle]
-pub extern "C" fn {{ cbi.ffi_init_callback().name() }}(callback: uniffi::ForeignCallback, _: &mut uniffi::RustCallStatus) {
- {{ foreign_callback_internals }}.set_callback(callback);
- // The call status should be initialized to CALL_SUCCESS, so no need to modify it.
-}
-
-// Make an implementation which will shell out to the foreign language.
-#[doc(hidden)]
-#[derive(Debug)]
-struct {{ trait_impl }} {
- handle: u64
-}
-
-impl {{ trait_impl }} {
- fn new(handle: u64) -> Self {
- Self { handle }
- }
-}
-
-impl Drop for {{ trait_impl }} {
- fn drop(&mut self) {
- {{ foreign_callback_internals }}.invoke_callback::<(), crate::UniFfiTag>(
- self.handle, uniffi::IDX_CALLBACK_FREE, Default::default()
- )
- }
-}
-
-uniffi::deps::static_assertions::assert_impl_all!({{ trait_impl }}: Send);
-
-impl r#{{ trait_name }} for {{ trait_impl }} {
+#[::uniffi::export_for_udl(callback_interface)]
+pub trait r#{{ cbi.name() }} {
{%- for meth in cbi.methods() %}
-
- {#- Method declaration #}
- fn r#{{ meth.name() -}}
- ({% call rs::arg_list_decl_with_prefix("&self", meth) %})
- {%- match (meth.return_type(), meth.throws_type()) %}
- {%- when (Some(return_type), None) %} -> {{ return_type.borrow()|type_rs }}
- {%- when (Some(return_type), Some(err)) %} -> ::std::result::Result<{{ return_type.borrow()|type_rs }}, {{ err|type_rs }}>
- {%- when (None, Some(err)) %} -> ::std::result::Result<(), {{ err|type_rs }}>
- {% else -%}
- {%- endmatch -%} {
- {#- Method body #}
-
- {#- Packing args into a RustBuffer #}
- {% if meth.arguments().len() == 0 -%}
- let args_buf = Vec::new();
- {% else -%}
- let mut args_buf = Vec::new();
- {% endif -%}
+ fn r#{{ meth.name() }}(
+ {% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
{%- for arg in meth.arguments() %}
- {{ arg.as_type().borrow()|ffi_trait("Lower") }}::write(r#{{ arg.name() }}, &mut args_buf);
- {%- endfor -%}
- let args_rbuf = uniffi::RustBuffer::from_vec(args_buf);
-
- {#- Calling into foreign code. #}
- {{ foreign_callback_internals }}.invoke_callback::<{{ meth|return_type }}, crate::UniFfiTag>(self.handle, {{ loop.index }}, args_rbuf)
- }
- {%- endfor %}
+ r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
+ {%- endfor %}
+ )
+ {%- match (meth.return_type(), meth.throws_type()) %}
+ {%- when (Some(return_type), None) %} -> {{ return_type|type_rs }};
+ {%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}>;
+ {%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}>;
+ {%- when (None, None) %};
+ {%- endmatch %}
+ {% endfor %}
}
-
-::uniffi::scaffolding_ffi_converter_callback_interface!(r#{{ trait_name }}, {{ trait_impl }});
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs
index 6b9f96f224..f918ef2f3a 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs
@@ -1,13 +1,12 @@
{#
-// For each enum declared in the UDL, we assume the caller has provided a corresponding
-// rust `enum`. We provide the traits for sending it across the FFI, which will fail to
-// compile if the provided struct has a different shape to the one declared in the UDL.
-//
-// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
-// public so other crates can refer to it via an `[External='crate'] typedef`
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
#}
-#[::uniffi::derive_enum_for_udl]
+#[::uniffi::derive_enum_for_udl(
+ {%- if e.is_non_exhaustive() -%}
+ non_exhaustive,
+ {%- endif %}
+)]
enum r#{{ e.name() }} {
{%- for variant in e.variants() %}
r#{{ variant.name() }} {
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
index 94538ecaa8..64f48e2334 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
@@ -1,10 +1,5 @@
{#
-// For each error declared in the UDL, we assume the caller has provided a corresponding
-// rust `enum`. We provide the traits for sending it across the FFI, which will fail to
-// compile if the provided struct has a different shape to the one declared in the UDL.
-//
-// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
-// public so other crates can refer to it via an `[External='crate'] typedef`
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
#}
#[::uniffi::derive_error_for_udl(
@@ -14,6 +9,9 @@
with_try_read,
{%- endif %}
{%- endif %}
+ {%- if e.is_non_exhaustive() -%}
+ non_exhaustive,
+ {%- endif %}
)]
enum r#{{ e.name() }} {
{%- for variant in e.variants() %}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs
index ade1578897..d67e172cc2 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs
@@ -10,6 +10,8 @@
::uniffi::ffi_converter_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag);
{%- when ExternalKind::Interface %}
::uniffi::ffi_converter_arc_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag);
+{%- when ExternalKind::Trait %}
+::uniffi::ffi_converter_arc_forward!(dyn r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag);
{%- endmatch %}
{% endif %}
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
index e2445c670d..e752878af5 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
@@ -1,24 +1,15 @@
-// For each Object definition, we assume the caller has provided an appropriately-shaped `struct T`
-// with an `impl` for each method on the object. We create an `Arc<T>` for "safely" handing out
-// references to these structs to foreign language code, and we provide a `pub extern "C"` function
-// corresponding to each method.
-//
-// (Note that "safely" is in "scare quotes" - that's because we use functions on an `Arc` that
-// that are inherently unsafe, but the code we generate is safe in practice.)
-//
-// If the caller's implementation of the struct does not match with the methods or types specified
-// in the UDL, then the rust compiler will complain with a (hopefully at least somewhat helpful!)
-// error message when processing this generated code.
+{#
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
+#}
-{%- match obj.imp() -%}
-{%- when ObjectImpl::Trait %}
-#[::uniffi::export_for_udl]
+{%- if obj.is_trait_interface() %}
+#[::uniffi::export_for_udl{% if obj.has_callback_interface() %}(with_foreign){% endif %}]
pub trait r#{{ obj.name() }} {
{%- for meth in obj.methods() %}
- fn {{ meth.name() }}(
+ {% if meth.is_async() %}async {% endif %}fn r#{{ meth.name() }}(
{% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
{%- for arg in meth.arguments() %}
- {{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
+ r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
{%- endfor %}
)
{%- match (meth.return_type(), meth.throws_type()) %}
@@ -29,7 +20,7 @@ pub trait r#{{ obj.name() }} {
{%- endmatch %}
{% endfor %}
}
-{% when ObjectImpl::Struct %}
+{%- else %}
{%- for tm in obj.uniffi_traits() %}
{% match tm %}
{% when UniffiTrait::Debug { fmt }%}
@@ -46,9 +37,10 @@ pub trait r#{{ obj.name() }} {
struct {{ obj.rust_name() }} { }
{%- for cons in obj.constructors() %}
-#[::uniffi::export_for_udl(constructor)]
+#[::uniffi::export_for_udl]
impl {{ obj.rust_name() }} {
- pub fn r#{{ cons.name() }}(
+ #[uniffi::constructor]
+ pub {% if cons.is_async() %}async {% endif %}fn r#{{ cons.name() }}(
{%- for arg in cons.arguments() %}
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
{%- endfor %}
@@ -68,7 +60,7 @@ impl {{ obj.rust_name() }} {
{%- for meth in obj.methods() %}
#[::uniffi::export_for_udl]
impl {{ obj.rust_name() }} {
- pub fn r#{{ meth.name() }}(
+ pub {% if meth.is_async() %}async {% endif %}fn r#{{ meth.name() }}(
{% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
{%- for arg in meth.arguments() %}
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
@@ -86,4 +78,4 @@ impl {{ obj.rust_name() }} {
}
{%- endfor %}
-{% endmatch %}
+{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs
index 85e131dd8c..a7affdf7b8 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs
@@ -1,11 +1,5 @@
{#
-// For each record declared in the UDL, we assume the caller has provided a corresponding
-// rust `struct` with the declared fields. We provide the traits for sending it across the FFI.
-// If the caller's struct does not match the shape and types declared in the UDL then the rust
-// compiler will complain with a type error.
-//
-// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
-// public so other crates can refer to it via an `[External='crate'] typedef`
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
#}
#[::uniffi::derive_record_for_udl]
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs
index eeee0f5ee2..27f3686b9f 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs
@@ -1,5 +1,8 @@
+{#
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
+#}
#[::uniffi::export_for_udl]
-pub fn r#{{ func.name() }}(
+pub {% if func.is_async() %}async {% endif %}fn r#{{ func.name() }}(
{%- for arg in func.arguments() %}
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
{%- endfor %}