summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/bindings
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/uniffi_bindgen/src/bindings
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings')
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs94
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs37
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs32
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs417
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs79
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs43
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt74
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt130
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt107
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt109
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt9
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt71
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt66
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt35
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt40
-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.kt100
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt41
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt66
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt23
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt45
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt38
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt17
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt94
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt82
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt48
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs107
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/mod.rs121
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs122
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs41
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs448
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs69
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py73
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py104
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py52
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py93
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py75
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py7
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py67
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py39
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py74
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py21
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py45
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py23
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py190
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py30
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py13
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py93
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py101
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py71
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/test.rs62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs273
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs47
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs50
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb59
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb117
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb17
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb73
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb20
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb246
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb301
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb218
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb73
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb47
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs98
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs35
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs476
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs87
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs99
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift20
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h55
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift60
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift154
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift86
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift60
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift83
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift84
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift22
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift88
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift20
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift62
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift183
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift21
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift37
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift34
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift15
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift90
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift97
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs225
154 files changed, 9450 insertions, 0 deletions
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
new file mode 100644
index 0000000000..c0c7ab29b9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
@@ -0,0 +1,33 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct CallbackInterfaceCodeType {
+ id: String,
+}
+
+impl CallbackInterfaceCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for CallbackInterfaceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn initialization_fn(&self, oracle: &dyn CodeOracle) -> Option<String> {
+ Some(format!("{}.register", self.ffi_converter_name(oracle)))
+ }
+}
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
new file mode 100644
index 0000000000..7d5f77141f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
@@ -0,0 +1,94 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal, TypeIdentifier};
+use paste::paste;
+
+fn render_literal(oracle: &dyn CodeOracle, literal: &Literal, inner: &TypeIdentifier) -> String {
+ match literal {
+ Literal::Null => "null".into(),
+ Literal::EmptySequence => "listOf()".into(),
+ Literal::EmptyMap => "mapOf()".into(),
+
+ // For optionals
+ _ => oracle.find(inner).literal(oracle, literal),
+ }
+}
+
+macro_rules! impl_code_type_for_compound {
+ ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => {
+ paste! {
+ pub struct $T {
+ inner: TypeIdentifier,
+ }
+
+ impl $T {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &TypeIdentifier {
+ &self.inner
+ }
+ }
+
+ impl CodeType for $T {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!($type_label_pattern, oracle.find(self.inner()).type_label(oracle))
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!($canonical_name_pattern, oracle.find(self.inner()).canonical_name(oracle))
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, literal, self.inner())
+ }
+ }
+ }
+ }
+ }
+
+impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}");
+impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}");
+
+pub struct MapCodeType {
+ key: TypeIdentifier,
+ value: TypeIdentifier,
+}
+
+impl MapCodeType {
+ pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self {
+ Self { key, value }
+ }
+
+ fn key(&self) -> &TypeIdentifier {
+ &self.key
+ }
+
+ fn value(&self) -> &TypeIdentifier {
+ &self.value
+ }
+}
+
+impl CodeType for MapCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Map<{}, {}>",
+ self.key().type_label(oracle),
+ self.value().type_label(oracle),
+ )
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Map{}{}",
+ self.key().type_label(oracle),
+ self.value().type_label(oracle),
+ )
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, literal, &self.value)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs
new file mode 100644
index 0000000000..4a5636a228
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs
@@ -0,0 +1,29 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct CustomCodeType {
+ name: String,
+}
+
+impl CustomCodeType {
+ pub fn new(name: String) -> Self {
+ CustomCodeType { name }
+ }
+}
+
+impl CodeType for CustomCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!("Can't have a literal of a custom type");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs
new file mode 100644
index 0000000000..941008e370
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs
@@ -0,0 +1,37 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct EnumCodeType {
+ id: String,
+}
+
+impl EnumCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for EnumCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ if let Literal::Enum(v, _) = literal {
+ format!(
+ "{}.{}",
+ self.type_label(oracle),
+ oracle.enum_variant_name(v)
+ )
+ } else {
+ unreachable!();
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs
new file mode 100644
index 0000000000..3ab5f0ac9f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/error.rs
@@ -0,0 +1,29 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct ErrorCodeType {
+ id: String,
+}
+
+impl ErrorCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ErrorCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.error_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+}
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
new file mode 100644
index 0000000000..32d47cb3f1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
@@ -0,0 +1,29 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct ExternalCodeType {
+ name: String,
+}
+
+impl ExternalCodeType {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+impl CodeType for ExternalCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!("Can't have a literal of an external type");
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs
new file mode 100644
index 0000000000..57b9a145e7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs
@@ -0,0 +1,32 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+use paste::paste;
+
+macro_rules! impl_code_type_for_miscellany {
+ ($T:ty, $class_name:literal, $canonical_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ $canonical_name.into()
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!()
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_miscellany!(TimestampCodeType, "java.time.Instant", "Timestamp");
+
+impl_code_type_for_miscellany!(DurationCodeType, "java.time.Duration", "Duration");
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
new file mode 100644
index 0000000000..8fe12f30c7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
@@ -0,0 +1,417 @@
+/* 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 std::borrow::Borrow;
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap, HashSet};
+
+use anyhow::{Context, Result};
+use askama::Template;
+use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
+use serde::{Deserialize, Serialize};
+
+use crate::backend::{CodeOracle, CodeType, TemplateExpression, TypeIdentifier};
+use crate::interface::*;
+use crate::MergeWith;
+
+mod callback_interface;
+mod compounds;
+mod custom;
+mod enum_;
+mod error;
+mod external;
+mod miscellany;
+mod object;
+mod primitives;
+mod record;
+
+// config options to customize the generated Kotlin.
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub struct Config {
+ package_name: Option<String>,
+ cdylib_name: Option<String>,
+ #[serde(default)]
+ custom_types: HashMap<String, CustomTypeConfig>,
+ #[serde(default)]
+ external_packages: HashMap<String, String>,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub struct CustomTypeConfig {
+ imports: Option<Vec<String>>,
+ type_name: Option<String>,
+ into_custom: TemplateExpression,
+ from_custom: TemplateExpression,
+}
+
+impl Config {
+ pub fn package_name(&self) -> String {
+ if let Some(package_name) = &self.package_name {
+ package_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+
+ pub fn cdylib_name(&self) -> String {
+ if let Some(cdylib_name) = &self.cdylib_name {
+ cdylib_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ package_name: Some(format!("uniffi.{}", ci.namespace())),
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ custom_types: HashMap::new(),
+ external_packages: HashMap::new(),
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ package_name: self.package_name.merge_with(&other.package_name),
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ custom_types: self.custom_types.merge_with(&other.custom_types),
+ external_packages: self.external_packages.merge_with(&other.external_packages),
+ }
+ }
+}
+
+// Generate kotlin bindings for the given ComponentInterface, as a string.
+pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
+ KotlinWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render kotlin bindings")
+}
+
+/// A struct to record a Kotlin import statement.
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub enum ImportRequirement {
+ /// The name we are importing.
+ Import { name: String },
+ /// Import the name with the specified local name.
+ ImportAs { name: String, as_name: String },
+}
+
+impl ImportRequirement {
+ /// Render the Kotlin import statement.
+ fn render(&self) -> String {
+ match &self {
+ ImportRequirement::Import { name } => format!("import {name}"),
+ ImportRequirement::ImportAs { name, as_name } => {
+ format!("import {name} as {as_name}")
+ }
+ }
+ }
+}
+
+/// Renders Kotlin helper code for all types
+///
+/// This template is a bit different than others in that it stores internal state from the render
+/// process. Make sure to only call `render()` once.
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "Types.kt")]
+pub struct TypeRenderer<'a> {
+ kotlin_config: &'a Config,
+ ci: &'a ComponentInterface,
+ // Track included modules for the `include_once()` macro
+ include_once_names: RefCell<HashSet<String>>,
+ // Track imports added with the `add_import()` macro
+ imports: RefCell<BTreeSet<ImportRequirement>>,
+}
+
+impl<'a> TypeRenderer<'a> {
+ fn new(kotlin_config: &'a Config, ci: &'a ComponentInterface) -> Self {
+ Self {
+ kotlin_config,
+ ci,
+ include_once_names: RefCell::new(HashSet::new()),
+ imports: RefCell::new(BTreeSet::new()),
+ }
+ }
+
+ // Get the package name for an external type
+ fn external_type_package_name(&self, crate_name: &str) -> String {
+ match self.kotlin_config.external_packages.get(crate_name) {
+ Some(name) => name.clone(),
+ None => crate_name.to_string(),
+ }
+ }
+
+ // The following methods are used by the `Types.kt` macros.
+
+ // Helper for the including a template, but only once.
+ //
+ // The first time this is called with a name it will return true, indicating that we should
+ // include the template. Subsequent calls will return false.
+ fn include_once_check(&self, name: &str) -> bool {
+ self.include_once_names
+ .borrow_mut()
+ .insert(name.to_string())
+ }
+
+ // Helper to add an import statement
+ //
+ // Call this inside your template to cause an import statement to be added at the top of the
+ // file. Imports will be sorted and de-deuped.
+ //
+ // Returns an empty string so that it can be used inside an askama `{{ }}` block.
+ fn add_import(&self, name: &str) -> &str {
+ self.imports.borrow_mut().insert(ImportRequirement::Import {
+ name: name.to_owned(),
+ });
+ ""
+ }
+
+ // Like add_import, but arranges for `import name as as_name`
+ fn add_import_as(&self, name: &str, as_name: &str) -> &str {
+ self.imports
+ .borrow_mut()
+ .insert(ImportRequirement::ImportAs {
+ name: name.to_owned(),
+ as_name: as_name.to_owned(),
+ });
+ ""
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "wrapper.kt")]
+pub struct KotlinWrapper<'a> {
+ config: Config,
+ ci: &'a ComponentInterface,
+ type_helper_code: String,
+ type_imports: BTreeSet<ImportRequirement>,
+}
+
+impl<'a> KotlinWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ let type_renderer = TypeRenderer::new(&config, ci);
+ let type_helper_code = type_renderer.render().unwrap();
+ let type_imports = type_renderer.imports.into_inner();
+ Self {
+ config,
+ ci,
+ type_helper_code,
+ type_imports,
+ }
+ }
+
+ pub fn initialization_fns(&self) -> Vec<String> {
+ self.ci
+ .iter_types()
+ .filter_map(|t| t.initialization_fn(&KotlinCodeOracle))
+ .collect()
+ }
+
+ pub fn imports(&self) -> Vec<ImportRequirement> {
+ self.type_imports.iter().cloned().collect()
+ }
+}
+
+#[derive(Clone)]
+pub struct KotlinCodeOracle;
+
+impl KotlinCodeOracle {
+ // Map `Type` instances to a `Box<dyn CodeType>` for that type.
+ //
+ // There is a companion match in `templates/Types.kt` which performs a similar function for the
+ // template code.
+ //
+ // - When adding additional types here, make sure to also add a match arm to the `Types.kt` template.
+ // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+ match type_ {
+ Type::UInt8 => Box::new(primitives::UInt8CodeType),
+ Type::Int8 => Box::new(primitives::Int8CodeType),
+ Type::UInt16 => Box::new(primitives::UInt16CodeType),
+ Type::Int16 => Box::new(primitives::Int16CodeType),
+ Type::UInt32 => Box::new(primitives::UInt32CodeType),
+ Type::Int32 => Box::new(primitives::Int32CodeType),
+ Type::UInt64 => Box::new(primitives::UInt64CodeType),
+ Type::Int64 => Box::new(primitives::Int64CodeType),
+ Type::Float32 => Box::new(primitives::Float32CodeType),
+ Type::Float64 => Box::new(primitives::Float64CodeType),
+ Type::Boolean => Box::new(primitives::BooleanCodeType),
+ Type::String => Box::new(primitives::StringCodeType),
+
+ Type::Timestamp => Box::new(miscellany::TimestampCodeType),
+ Type::Duration => Box::new(miscellany::DurationCodeType),
+
+ Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+ Type::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+ Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
+ Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
+ Type::CallbackInterface(id) => {
+ Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
+ }
+ Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)),
+ Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)),
+ Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)),
+ Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
+ Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling create_code_type")
+ }
+ }
+ }
+}
+
+impl CodeOracle for KotlinCodeOracle {
+ fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+ self.create_code_type(type_.clone())
+ }
+
+ /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, nm: &str) -> String {
+ nm.to_string().to_upper_camel_case()
+ }
+
+ /// Get the idiomatic Kotlin rendering of a function name.
+ fn fn_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Kotlin rendering of a variable name.
+ fn var_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Kotlin rendering of an individual enum variant.
+ fn enum_variant_name(&self, nm: &str) -> String {
+ nm.to_string().to_shouty_snake_case()
+ }
+
+ /// Get the idiomatic Kotlin rendering of an exception name
+ ///
+ /// This replaces "Error" at the end of the name with "Exception". Rust code typically uses
+ /// "Error" for any type of error but in the Java world, "Error" means a non-recoverable error
+ /// and is distinguished from an "Exception".
+ fn error_name(&self, nm: &str) -> String {
+ // errors are a class in kotlin.
+ let name = self.class_name(nm);
+ match name.strip_suffix("Error") {
+ None => name,
+ Some(stripped) => format!("{stripped}Exception"),
+ }
+ }
+
+ 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
+ // types from the component API.
+ FfiType::Int8 | FfiType::UInt8 => "Byte".to_string(),
+ FfiType::Int16 | FfiType::UInt16 => "Short".to_string(),
+ FfiType::Int32 | FfiType::UInt32 => "Int".to_string(),
+ FfiType::Int64 | FfiType::UInt64 => "Long".to_string(),
+ FfiType::Float32 => "Float".to_string(),
+ FfiType::Float64 => "Double".to_string(),
+ FfiType::RustArcPtr(_) => "Pointer".to_string(),
+ FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
+ Some(suffix) => format!("RustBuffer{}.ByValue", suffix),
+ None => "RustBuffer.ByValue".to_string(),
+ },
+ FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
+ FfiType::ForeignCallback => "ForeignCallback".to_string(),
+ }
+ }
+}
+
+pub mod filters {
+ use super::*;
+
+ fn oracle() -> &'static KotlinCodeOracle {
+ &KotlinCodeOracle
+ }
+
+ pub fn type_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.type_label(oracle()))
+ }
+
+ pub fn canonical_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.canonical_name(oracle()))
+ }
+
+ pub fn ffi_converter_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.ffi_converter_name(oracle()))
+ }
+
+ pub fn lower_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lower", codetype.ffi_converter_name(oracle())))
+ }
+
+ pub fn allocation_size_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!(
+ "{}.allocationSize",
+ codetype.ffi_converter_name(oracle())
+ ))
+ }
+
+ pub fn write_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.write", codetype.ffi_converter_name(oracle())))
+ }
+
+ pub fn lift_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lift", codetype.ffi_converter_name(oracle())))
+ }
+
+ pub fn read_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.read", codetype.ffi_converter_name(oracle())))
+ }
+
+ pub fn render_literal(
+ literal: &Literal,
+ codetype: &impl CodeType,
+ ) -> Result<String, askama::Error> {
+ Ok(codetype.literal(oracle(), literal))
+ }
+
+ /// Get the Kotlin syntax for representing a given low-level `FfiType`.
+ pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_type_label(type_))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
+ pub fn class_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().class_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a function name.
+ pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().fn_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a variable name.
+ pub fn var_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().var_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of an individual enum variant.
+ pub fn enum_variant(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().enum_variant_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of an exception name, replacing
+ /// `Error` with `Exception`.
+ pub fn exception_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().error_name(nm))
+ }
+
+ /// Remove the "`" chars we put around function/variable names
+ ///
+ /// These are used to avoid name clashes with kotlin identifiers, but sometimes you want to
+ /// render the name unquoted. One example is the message property for errors where we want to
+ /// display the name for the user.
+ pub fn unquote(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.trim_matches('`').to_string())
+ }
+}
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
new file mode 100644
index 0000000000..7a1cddbafd
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
@@ -0,0 +1,29 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct ObjectCodeType {
+ id: String,
+}
+
+impl ObjectCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ObjectCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+}
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
new file mode 100644
index 0000000000..afc8fc556d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
@@ -0,0 +1,79 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+use crate::interface::{types::Type, Radix};
+use paste::paste;
+
+fn render_literal(_oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ fn typed_number(type_: &Type, num_str: String) -> String {
+ match 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"),
+
+ Type::UInt8 | Type::UInt16 | Type::UInt32 => format!("{num_str}u"),
+ Type::UInt64 => format!("{num_str}uL"),
+
+ Type::Float32 => format!("{num_str}f"),
+ Type::Float64 => num_str,
+ _ => panic!("Unexpected literal: {num_str} is not a number"),
+ }
+ }
+
+ match literal {
+ Literal::Boolean(v) => format!("{v}"),
+ Literal::String(s) => format!("\"{s}\""),
+ Literal::Int(i, radix, type_) => typed_number(
+ type_,
+ match radix {
+ Radix::Octal => format!("{i:#x}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ ),
+ Literal::UInt(i, radix, type_) => typed_number(
+ type_,
+ match radix {
+ Radix::Octal => format!("{i:#x}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ ),
+ Literal::Float(string, type_) => typed_number(type_, string.clone()),
+
+ _ => unreachable!("Literal"),
+ }
+}
+
+macro_rules! impl_code_type_for_primitive {
+ ($T:ty, $class_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, &literal)
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_primitive!(BooleanCodeType, "Boolean");
+impl_code_type_for_primitive!(StringCodeType, "String");
+impl_code_type_for_primitive!(Int8CodeType, "Byte");
+impl_code_type_for_primitive!(Int16CodeType, "Short");
+impl_code_type_for_primitive!(Int32CodeType, "Int");
+impl_code_type_for_primitive!(Int64CodeType, "Long");
+impl_code_type_for_primitive!(UInt8CodeType, "UByte");
+impl_code_type_for_primitive!(UInt16CodeType, "UShort");
+impl_code_type_for_primitive!(UInt32CodeType, "UInt");
+impl_code_type_for_primitive!(UInt64CodeType, "ULong");
+impl_code_type_for_primitive!(Float32CodeType, "Float");
+impl_code_type_for_primitive!(Float64CodeType, "Double");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs
new file mode 100644
index 0000000000..f923f102c4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs
@@ -0,0 +1,29 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct RecordCodeType {
+ id: String,
+}
+
+impl RecordCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for RecordCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs
new file mode 100644
index 0000000000..c372eab83b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs
@@ -0,0 +1,43 @@
+/* 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 anyhow::Result;
+use camino::{Utf8Path, Utf8PathBuf};
+use fs_err::{self as fs, File};
+use std::{io::Write, process::Command};
+
+pub mod gen_kotlin;
+pub use gen_kotlin::{generate_bindings, Config};
+mod test;
+
+use super::super::interface::ComponentInterface;
+pub use test::run_test;
+
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let mut kt_file = full_bindings_path(config, out_dir);
+ fs::create_dir_all(&kt_file)?;
+ kt_file.push(format!("{}.kt", ci.namespace()));
+ let mut f = File::create(&kt_file)?;
+ write!(f, "{}", generate_bindings(config, ci)?)?;
+ if try_format_code {
+ if let Err(e) = Command::new("ktlint").arg("-F").arg(&kt_file).output() {
+ println!(
+ "Warning: Unable to auto-format {} using ktlint: {:?}",
+ kt_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+ Ok(())
+}
+
+fn full_bindings_path(config: &Config, out_dir: &Utf8Path) -> Utf8PathBuf {
+ let package_path: Utf8PathBuf = config.package_name().split('.').collect();
+ Utf8PathBuf::from(out_dir).join(package_path)
+}
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
new file mode 100644
index 0000000000..8cfa2ce000
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterBoolean: FfiConverter<Boolean, Byte> {
+ override fun lift(value: Byte): Boolean {
+ return value.toInt() != 0
+ }
+
+ override fun read(buf: ByteBuffer): Boolean {
+ return lift(buf.get())
+ }
+
+ override fun lower(value: Boolean): Byte {
+ return if (value) 1.toByte() else 0.toByte()
+ }
+
+ override fun allocationSize(value: Boolean) = 1
+
+ override fun write(value: Boolean, buf: ByteBuffer) {
+ buf.put(lower(value))
+ }
+}
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
new file mode 100644
index 0000000000..3f5d72a608
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
@@ -0,0 +1,74 @@
+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 invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, 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
+
+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)
+
+ fun drop(handle: Handle): RustBuffer.ByValue {
+ return handleMap.remove(handle).let { RustBuffer.ByValue() }
+ }
+
+ override fun lift(value: Handle): CallbackInterface {
+ return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug")
+ }
+
+ 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 allocationSize(value: CallbackInterface) = 8
+
+ 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
new file mode 100644
index 0000000000..c7c4d2b973
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
@@ -0,0 +1,130 @@
+{%- let cbi = ci.get_callback_interface_definition(name).unwrap() %}
+{%- let type_name = cbi|type_name %}
+{%- 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 -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+}
+
+// The ForeignCallback that is passed to Rust.
+internal class {{ foreign_callback }} : ForeignCallback {
+ @Suppress("TooGenericExceptionCaught")
+ override fun invoke(handle: Handle, method: Int, args: RustBuffer.ByValue, outBuf: RustBufferByReference): Int {
+ val cb = {{ ffi_converter_name }}.lift(handle)
+ return when (method) {
+ IDX_CALLBACK_FREE -> {
+ {{ ffi_converter_name }}.drop(handle)
+ // No return value.
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ 0
+ }
+ {% 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/src/ffi/foreigncallbacks.rs` for info
+ try {
+ {%- match meth.throws_type() %}
+ {%- when Some(error_type) %}
+ try {
+ val buffer = this.{{ method_name }}(cb, args)
+ // Success
+ outBuf.setValue(buffer)
+ 1
+ } catch (e: {{ error_type|type_name }}) {
+ // Expected error
+ val buffer = {{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e)
+ outBuf.setValue(buffer)
+ -2
+ }
+ {%- else %}
+ val buffer = this.{{ method_name }}(cb, args)
+ // Success
+ outBuf.setValue(buffer)
+ 1
+ {%- endmatch %}
+ } 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
+ }
+ -1
+ }
+ }
+ {% endfor %}
+ else -> {
+ // An unexpected error happened.
+ // See docs of ForeignCallback in `uniffi/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
+ }
+ -1
+ }
+ }
+ }
+
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
+ private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, args: RustBuffer.ByValue): RustBuffer.ByValue =
+ try {
+ {#- Unpacking args from the RustBuffer #}
+ {%- if meth.arguments().len() != 0 -%}
+ {#- Calling the concrete callback object #}
+ val buf = args.asByteBuffer() ?: throw InternalException("No ByteBuffer in RustBuffer; this is a Uniffi bug")
+ kotlinCallbackInterface.{{ meth.name()|fn_name }}(
+ {% for arg in meth.arguments() -%}
+ {{ arg|read_fn }}(buf)
+ {%- if !loop.last %}, {% endif %}
+ {% endfor -%}
+ )
+ {% else %}
+ kotlinCallbackInterface.{{ meth.name()|fn_name }}()
+ {% endif -%}
+
+ {#- Packing up the return value into a RustBuffer #}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ .let {
+ {{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(it)
+ }
+ {%- else -%}
+ .let { RustBuffer.ByValue() }
+ {% endmatch -%}
+ // TODO catch errors and report them back to Rust.
+ // https://github.com/mozilla/uniffi-rs/issues/351
+ } finally {
+ RustBuffer.free(args)
+ }
+
+ {% 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)
+ }
+ }
+}
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
new file mode 100644
index 0000000000..c7807f2f33
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
@@ -0,0 +1,62 @@
+{%- match kotlin_config.custom_types.get(name.as_str()) %}
+{%- when None %}
+{#- Define the type using typealiases to the builtin #}
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias {{ name }} = {{ builtin|type_name }}
+public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }}
+
+{%- when Some with (config) %}
+
+{%- let ffi_type_name=builtin.ffi_type().borrow()|ffi_type_name %}
+
+{# When the config specifies a different type name, create a typealias for it #}
+{%- match config.type_name %}
+{%- when Some(concrete_type_name) %}
+/**
+ * Typealias from the type name used in the UDL file to the custom type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ * It's also what we have an external type that references a custom type.
+ */
+public typealias {{ name }} = {{ concrete_type_name }}
+{%- else %}
+{%- endmatch %}
+
+{%- match config.imports %}
+{%- when Some(imports) %}
+{%- for import_name in imports %}
+{{ self.add_import(import_name) }}
+{%- endfor %}
+{%- else %}
+{%- endmatch %}
+
+public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_name }}> {
+ override fun lift(value: {{ ffi_type_name }}): {{ name }} {
+ val builtinValue = {{ builtin|lift_fn }}(value)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ override fun lower(value: {{ name }}): {{ ffi_type_name }} {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|lower_fn }}(builtinValue)
+ }
+
+ override fun read(buf: ByteBuffer): {{ name }} {
+ val builtinValue = {{ builtin|read_fn }}(buf)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ override fun allocationSize(value: {{ name }}): Int {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|allocation_size_fn }}(builtinValue)
+ }
+
+ override fun write(value: {{ name }}, buf: ByteBuffer) {
+ val builtinValue = {{ config.from_custom.render("value") }}
+ {{ builtin|write_fn }}(builtinValue, buf)
+ }
+}
+{%- endmatch %}
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
new file mode 100644
index 0000000000..4237c6f9a8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
@@ -0,0 +1,36 @@
+public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> {
+ override fun read(buf: ByteBuffer): java.time.Duration {
+ // Type mismatch (should be u64) but we check for overflow/underflow below
+ val seconds = buf.getLong()
+ // Type mismatch (should be u32) but we check for overflow/underflow below
+ val nanoseconds = buf.getInt().toLong()
+ if (seconds < 0) {
+ throw java.time.DateTimeException("Duration exceeds minimum or maximum value supported by uniffi")
+ }
+ if (nanoseconds < 0) {
+ throw java.time.DateTimeException("Duration nanoseconds exceed minimum or maximum supported by uniffi")
+ }
+ return java.time.Duration.ofSeconds(seconds, nanoseconds)
+ }
+
+ // 8 bytes for seconds, 4 bytes for nanoseconds
+ override fun allocationSize(value: java.time.Duration) = 12
+
+ override fun write(value: java.time.Duration, buf: ByteBuffer) {
+ if (value.seconds < 0) {
+ // Rust does not support negative Durations
+ throw IllegalArgumentException("Invalid duration, must be non-negative")
+ }
+
+ if (value.nano < 0) {
+ // Java docs provide guarantee that nano will always be positive, so this should be impossible
+ // See: https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html
+ throw IllegalArgumentException("Invalid duration, nano value must be non-negative")
+ }
+
+ // Type mismatch (should be u64) but since Rust doesn't support negative durations we should be OK
+ buf.putLong(value.seconds)
+ // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK
+ buf.putInt(value.nano)
+ }
+}
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
new file mode 100644
index 0000000000..be198ac7b9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
@@ -0,0 +1,107 @@
+{#
+// Kotlin's `enum class` construct doesn't support variants with associated data,
+// but is a little nicer for consumers than its `sealed class` enum pattern.
+// So, we switch here, using `enum class` for enums with no associated data
+// and `sealed class` for the general case.
+#}
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+
+{%- if e.is_flat() %}
+
+enum class {{ type_name }} {
+ {% for variant in e.variants() -%}
+ {{ variant.name()|enum_variant }}{% if loop.last %};{% else %},{% endif %}
+ {%- endfor %}
+}
+
+public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer) = try {
+ {{ type_name }}.values()[buf.getInt() - 1]
+ } catch (e: IndexOutOfBoundsException) {
+ throw RuntimeException("invalid enum value, something is very wrong!!", e)
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = 4
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ buf.putInt(value.ordinal + 1)
+ }
+}
+
+{% else %}
+
+sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} {
+ {% for variant in e.variants() -%}
+ {% if !variant.has_fields() -%}
+ object {{ variant.name()|class_name }} : {{ type_name }}()
+ {% else -%}
+ data class {{ variant.name()|class_name }}(
+ {% for field in variant.fields() -%}
+ val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %}
+ {% endfor -%}
+ ) : {{ type_name }}()
+ {%- endif %}
+ {% endfor %}
+
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ when(this) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|class_name }} -> {
+ {%- if variant.has_fields() %}
+ {% call kt::destroy_fields(variant) %}
+ {% else -%}
+ // Nothing to destroy
+ {%- endif %}
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+ {% endif %}
+}
+
+public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}>{
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant.name()|class_name }}{% if variant.has_fields() %}(
+ {% for field in variant.fields() -%}
+ {{ field|read_fn }}(buf),
+ {% endfor -%}
+ ){%- endif -%}
+ {%- endfor %}
+ else -> throw RuntimeException("invalid enum value, something is very wrong!!")
+ }
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|class_name }} -> {
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ (
+ 4
+ {%- for field in variant.fields() %}
+ + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ )
+ }
+ {%- endfor %}
+ }
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|class_name }} -> {
+ buf.putInt({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ Unit
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+}
+
+{% endif %}
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
new file mode 100644
index 0000000000..9ddef9bb73
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
@@ -0,0 +1,109 @@
+{%- let e = ci.get_error_definition(name).unwrap() %}
+
+{% if e.is_flat() %}
+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() -%}
+ class {{ variant.name()|exception_name }}(message: String) : {{ type_name }}(message)
+ {% endfor %}
+
+ companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ e|lift_fn }}(error_buf)
+ }
+}
+{%- else %}
+sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} {
+ // Each variant is a nested class
+ {% for variant in e.variants() -%}
+ {%- let variant_name = variant.name()|exception_name %}
+ class {{ variant_name }}(
+ {% for field in variant.fields() -%}
+ val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %}
+ {% endfor -%}
+ ) : {{ type_name }}() {
+ override val message
+ get() = "{%- for field in variant.fields() %}{{ field.name()|var_name|unquote }}=${ {{field.name()|var_name }} }{% if !loop.last %}, {% endif %}{% endfor %}"
+ }
+ {% endfor %}
+
+ companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ e|lift_fn }}(error_buf)
+ }
+
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ when(this) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|exception_name }} -> {
+ {%- if variant.has_fields() %}
+ {% call kt::destroy_fields(variant) %}
+ {% else -%}
+ // Nothing to destroy
+ {%- endif %}
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+ {% endif %}
+}
+{%- endif %}
+
+public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ {% if e.is_flat() %}
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant.name()|exception_name }}({{ TypeIdentifier::String.borrow()|read_fn }}(buf))
+ {%- endfor %}
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+ {% else %}
+
+ return when(buf.getInt()) {
+ {%- for variant in e.variants() %}
+ {{ loop.index }} -> {{ type_name }}.{{ variant.name()|exception_name }}({% if variant.has_fields() %}
+ {% for field in variant.fields() -%}
+ {{ field|read_fn }}(buf),
+ {% endfor -%}
+ {%- endif -%})
+ {%- endfor %}
+ else -> throw RuntimeException("invalid error enum value, something is very wrong!!")
+ }
+ {%- endif %}
+ }
+
+ override fun allocationSize(value: {{ type_name }}): Int {
+ {%- if e.is_flat() %}
+ return 4
+ {%- else %}
+ return when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|exception_name }} -> (
+ // Add the size for the Int that specifies the variant plus the size needed for all fields
+ 4
+ {%- for field in variant.fields() %}
+ + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ )
+ {%- endfor %}
+ }
+ {%- endif %}
+ }
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ when(value) {
+ {%- for variant in e.variants() %}
+ is {{ type_name }}.{{ variant.name()|exception_name }} -> {
+ buf.putInt({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ Unit
+ }
+ {%- endfor %}
+ }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ }
+ }
+
+}
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
new file mode 100644
index 0000000000..21d96dcdf1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
@@ -0,0 +1,9 @@
+{%- let package_name=self.external_type_package_name(crate_name) %}
+{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %}
+{%- 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) %}
+
+{{- self.add_import(fully_qualified_type_name) }}
+{{- self.add_import(fully_qualified_ffi_converter_name) }}
+{{ self.add_import_as(fully_qualified_rustbuffer_name, local_rustbuffer_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
new file mode 100644
index 0000000000..3b2c9d225a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
@@ -0,0 +1,71 @@
+// The FfiConverter interface handles converter types to and from the FFI
+//
+// All implementing objects should be public to support external types. When a
+// type is external we need to import it's FfiConverter.
+public interface FfiConverter<KotlinType, FfiType> {
+ // Convert an FFI type to a Kotlin type
+ fun lift(value: FfiType): KotlinType
+
+ // Convert an Kotlin type to an FFI type
+ fun lower(value: KotlinType): FfiType
+
+ // Read a Kotlin type from a `ByteBuffer`
+ fun read(buf: ByteBuffer): KotlinType
+
+ // Calculate bytes to allocate when creating a `RustBuffer`
+ //
+ // This must return at least as many bytes as the write() function will
+ // write. It can return more bytes than needed, for example when writing
+ // Strings we can't know the exact bytes needed until we the UTF-8
+ // 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
+
+ // Write a Kotlin type to a `ByteBuffer`
+ fun write(value: KotlinType, buf: ByteBuffer)
+
+ // Lower a value into a `RustBuffer`
+ //
+ // This method lowers a value into a `RustBuffer` rather than the normal
+ // FfiType. It's used by the callback interface code. Callback interface
+ // returns are always serialized into a `RustBuffer` regardless of their
+ // normal FFI type.
+ fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
+ val rbuf = RustBuffer.alloc(allocationSize(value))
+ try {
+ val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+ write(value, bbuf)
+ rbuf.writeField("len", bbuf.position())
+ return rbuf
+ } catch (e: Throwable) {
+ RustBuffer.free(rbuf)
+ throw e
+ }
+ }
+
+ // Lift a value from a `RustBuffer`.
+ //
+ // This here mostly because of the symmetry with `lowerIntoRustBuffer()`.
+ // It's currently only used by the `FfiConverterRustBuffer` class below.
+ fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType {
+ val byteBuf = rbuf.asByteBuffer()!!
+ try {
+ val item = read(byteBuf)
+ if (byteBuf.hasRemaining()) {
+ throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!")
+ }
+ return item
+ } finally {
+ RustBuffer.free(rbuf)
+ }
+ }
+}
+
+// FfiConverter that uses `RustBuffer` as the FfiType
+public interface FfiConverterRustBuffer<KotlinType>: FfiConverter<KotlinType, RustBuffer.ByValue> {
+ override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value)
+ override fun lower(value: KotlinType) = lowerIntoRustBuffer(value)
+}
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
new file mode 100644
index 0000000000..eafec5d122
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterFloat: FfiConverter<Float, Float> {
+ override fun lift(value: Float): Float {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Float {
+ return buf.getFloat()
+ }
+
+ override fun lower(value: Float): Float {
+ return value
+ }
+
+ override fun allocationSize(value: Float) = 4
+
+ 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
new file mode 100644
index 0000000000..9fc2892c95
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterDouble: FfiConverter<Double, Double> {
+ override fun lift(value: Double): Double {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Double {
+ return buf.getDouble()
+ }
+
+ override fun lower(value: Double): Double {
+ return value
+ }
+
+ override fun allocationSize(value: Double) = 8
+
+ override fun write(value: Double, buf: ByteBuffer) {
+ buf.putDouble(value)
+ }
+}
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
new file mode 100644
index 0000000000..d7e61ef4c7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
@@ -0,0 +1,66 @@
+// 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.
+@Structure.FieldOrder("code", "error_buf")
+internal open class RustCallStatus : Structure() {
+ @JvmField var code: Int = 0
+ @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
+
+ fun isSuccess(): Boolean {
+ return code == 0
+ }
+
+ fun isError(): Boolean {
+ return code == 1
+ }
+
+ fun isPanic(): Boolean {
+ return code == 2
+ }
+}
+
+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> {
+ fun lift(error_buf: RustBuffer.ByValue): E;
+}
+
+// Helpers for calling Rust
+// In practice we usually need to be synchronized to call this safely, so it doesn't
+// 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();
+ val return_value = callback(status)
+ if (status.isSuccess()) {
+ return return_value
+ } else if (status.isError()) {
+ throw errorHandler.lift(status.error_buf)
+ } else if (status.isPanic()) {
+ // 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.
+ if (status.error_buf.len > 0) {
+ throw InternalException({{ TypeIdentifier::String.borrow()|lift_fn }}(status.error_buf))
+ } else {
+ throw InternalException("Rust panic")
+ }
+ } else {
+ throw InternalException("Unknown rust call status: $status.code")
+ }
+}
+
+// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
+object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
+ override fun lift(error_buf: RustBuffer.ByValue): InternalException {
+ RustBuffer.free(error_buf)
+ return InternalException("Unexpected CALL_ERROR")
+ }
+}
+
+// Call a rust function that returns a plain value
+private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
+ return rustCallWithError(NullCallStatusErrorHandler, callback);
+}
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
new file mode 100644
index 0000000000..75564276be
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterShort: FfiConverter<Short, Short> {
+ override fun lift(value: Short): Short {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Short {
+ return buf.getShort()
+ }
+
+ override fun lower(value: Short): Short {
+ return value
+ }
+
+ override fun allocationSize(value: Short) = 2
+
+ 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
new file mode 100644
index 0000000000..b7a8131c8b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterInt: FfiConverter<Int, Int> {
+ override fun lift(value: Int): Int {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Int {
+ return buf.getInt()
+ }
+
+ override fun lower(value: Int): Int {
+ return value
+ }
+
+ override fun allocationSize(value: Int) = 4
+
+ 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
new file mode 100644
index 0000000000..601cfc7c2c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterLong: FfiConverter<Long, Long> {
+ override fun lift(value: Long): Long {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Long {
+ return buf.getLong()
+ }
+
+ override fun lower(value: Long): Long {
+ return value
+ }
+
+ override fun allocationSize(value: Long) = 8
+
+ 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
new file mode 100644
index 0000000000..9237768dbf
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterByte: FfiConverter<Byte, Byte> {
+ override fun lift(value: Byte): Byte {
+ return value
+ }
+
+ override fun read(buf: ByteBuffer): Byte {
+ return buf.get()
+ }
+
+ override fun lower(value: Byte): Byte {
+ return value
+ }
+
+ override fun allocationSize(value: Byte) = 1
+
+ override fun write(value: Byte, buf: ByteBuffer) {
+ buf.put(value)
+ }
+}
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
new file mode 100644
index 0000000000..c8cbecb68b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
@@ -0,0 +1,35 @@
+{%- let key_type_name = key_type|type_name %}
+{%- let value_type_name = value_type|type_name %}
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_name }}, {{ value_type_name }}>> {
+ override fun read(buf: ByteBuffer): Map<{{ key_type_name }}, {{ value_type_name }}> {
+ // TODO: Once Kotlin's `buildMap` API is stabilized we should use it here.
+ val items : MutableMap<{{ key_type_name }}, {{ value_type_name }}> = mutableMapOf()
+ val len = buf.getInt()
+ repeat(len) {
+ val k = {{ key_type|read_fn }}(buf)
+ val v = {{ value_type|read_fn }}(buf)
+ items[k] = v
+ }
+ return items
+ }
+
+ override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int {
+ val spaceForMapSize = 4
+ val spaceForChildren = value.map { (k, v) ->
+ {{ key_type|allocation_size_fn }}(k) +
+ {{ value_type|allocation_size_fn }}(v)
+ }.sum()
+ return spaceForMapSize + spaceForChildren
+ }
+
+ override fun write(value: Map<{{ key_type_name }}, {{ value_type_name }}>, buf: ByteBuffer) {
+ buf.putInt(value.size)
+ // The parens on `(k, v)` here ensure we're calling the right method,
+ // which is important for compatibility with older android devices.
+ // Ref https://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/
+ value.forEach { (k, v) ->
+ {{ key_type|write_fn }}(k, buf)
+ {{ value_type|write_fn }}(v, buf)
+ }
+ }
+}
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
new file mode 100644
index 0000000000..df96ee5cbb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
@@ -0,0 +1,40 @@
+@Synchronized
+private fun findLibraryName(componentName: String): String {
+ val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride")
+ if (libOverride != null) {
+ return libOverride
+ }
+ return "{{ config.cdylib_name() }}"
+}
+
+private inline fun <reified Lib : Library> loadIndirect(
+ componentName: String
+): Lib {
+ return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
+}
+
+// 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 {
+ companion object {
+ internal val INSTANCE: _UniFFILib by lazy {
+ loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}")
+ {% let initialization_fns = self.initialization_fns() %}
+ {%- if !initialization_fns.is_empty() -%}
+ .also { lib: _UniFFILib ->
+ {% for fn in initialization_fns -%}
+ {{ fn }}(lib)
+ {% endfor -%}
+ }
+ {% endif %}
+ }
+ }
+
+ {% for func in ci.iter_ffi_function_definitions() -%}
+ fun {{ func.name() }}(
+ {%- call kt::arg_list_ffi_decl(func) %}
+ ){%- match func.return_type() -%}{%- when Some with (type_) %}: {{ type_.borrow()|ffi_type_name }}{% when None %}: Unit{% endmatch %}
+
+ {% endfor %}
+}
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
new file mode 100644
index 0000000000..b9352c690f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
@@ -0,0 +1,161 @@
+// 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
new file mode 100644
index 0000000000..5d654d9fae
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
@@ -0,0 +1,100 @@
+{%- let obj = ci.get_object_definition(name).unwrap() %}
+{%- 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") }}
+
+public interface {{ type_name }}Interface {
+ {% for meth in obj.methods() -%}
+ {%- match meth.throws_type() -%}
+ {%- when Some with (throwable) %}
+ @Throws({{ throwable|type_name }}::class)
+ {%- else -%}
+ {%- endmatch %}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+}
+
+class {{ type_name }}(
+ pointer: Pointer
+) : FFIObject(pointer), {{ type_name }}Interface {
+
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ constructor({% call kt::arg_list_decl(cons) -%}) :
+ this({% call kt::to_ffi_call(cons) %})
+ {%- 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)
+ }
+ }
+
+ {% for meth in obj.methods() -%}
+ {%- match meth.throws_type() -%}
+ {%- when Some with (throwable) %}
+ @Throws({{ throwable|type_name }}::class)
+ {%- else -%}
+ {%- endmatch %}
+ {%- 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 }} =
+ callWithPointer {
+ {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ }.let {
+ {{ return_type|lift_fn }}(it)
+ }
+
+ {%- when None -%}
+ override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) =
+ callWithPointer {
+ {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ }
+ {% endmatch %}
+ {% endfor %}
+
+ {% 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) %})
+ {% endfor %}
+ }
+ {% endif %}
+}
+
+public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
+ override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it }
+
+ override fun lift(value: Pointer): {{ type_name }} {
+ return {{ type_name }}(value)
+ }
+
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ // The Rust code always writes pointers as 8 bytes, and will
+ // fail to compile if they don't fit.
+ return lift(Pointer(buf.getLong()))
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = 8
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ // The Rust code always expects pointers written as 8 bytes,
+ // and will fail to compile if they don't fit.
+ buf.putLong(Pointer.nativeValue(lower(value)))
+ }
+}
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
new file mode 100644
index 0000000000..6830b51863
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
@@ -0,0 +1,27 @@
+{%- let inner_type_name = inner_type|type_name %}
+
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_name }}?> {
+ override fun read(buf: ByteBuffer): {{ inner_type_name }}? {
+ if (buf.get().toInt() == 0) {
+ return null
+ }
+ return {{ inner_type|read_fn }}(buf)
+ }
+
+ override fun allocationSize(value: {{ inner_type_name }}?): Int {
+ if (value == null) {
+ return 1
+ } else {
+ return 1 + {{ inner_type|allocation_size_fn }}(value)
+ }
+ }
+
+ override fun write(value: {{ inner_type_name }}?, buf: ByteBuffer) {
+ if (value == null) {
+ buf.put(0)
+ } else {
+ buf.put(1)
+ {{ inner_type|write_fn }}(value, buf)
+ }
+ }
+}
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
new file mode 100644
index 0000000000..a17f4d42eb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
@@ -0,0 +1,41 @@
+{%- let rec = ci.get_record_definition(name).unwrap() %}
+
+data class {{ type_name }} (
+ {%- for field in rec.fields() %}
+ var {{ field.name()|var_name }}: {{ field|type_name -}}
+ {%- match field.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|render_literal(field) }}
+ {%- else %}
+ {%- endmatch -%}
+ {% if !loop.last %}, {% endif %}
+ {%- endfor %}
+) {% if contains_object_references %}: Disposable {% endif %}{
+ {% if contains_object_references %}
+ @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here
+ override fun destroy() {
+ {% call kt::destroy_fields(rec) %}
+ }
+ {% endif %}
+}
+
+public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
+ override fun read(buf: ByteBuffer): {{ type_name }} {
+ return {{ type_name }}(
+ {%- for field in rec.fields() %}
+ {{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+ }
+
+ override fun allocationSize(value: {{ type_name }}) = (
+ {%- for field in rec.fields() %}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%}
+ {%- endfor %}
+ )
+
+ override fun write(value: {{ type_name }}, buf: ByteBuffer) {
+ {%- for field in rec.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ }
+}
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
new file mode 100644
index 0000000000..6cc218e940
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
@@ -0,0 +1,66 @@
+// This is a helper for safely working with byte buffers returned from the Rust code.
+// A rust-owned buffer is represented by its capacity, its current length, and a
+// pointer to the underlying data.
+
+@Structure.FieldOrder("capacity", "len", "data")
+open class RustBuffer : Structure() {
+ @JvmField var capacity: Int = 0
+ @JvmField var len: Int = 0
+ @JvmField var data: Pointer? = null
+
+ class ByValue : RustBuffer(), Structure.ByValue
+ class ByReference : RustBuffer(), Structure.ByReference
+
+ companion object {
+ internal fun alloc(size: Int = 0) = rustCall() { status ->
+ _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status).also {
+ if(it.data == null) {
+ throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
+ }
+ }
+ }
+
+ internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
+ _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
+ }
+ }
+
+ @Suppress("TooGenericExceptionThrown")
+ fun asByteBuffer() =
+ this.data?.getByteBuffer(0, this.len.toLong())?.also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+}
+
+/**
+ * The equivalent of the `*mut RustBuffer` type.
+ * Required for callbacks taking in an out pointer.
+ *
+ * Size is the sum of all values in the struct.
+ */
+class RustBufferByReference : ByReference(16) {
+ /**
+ * Set the pointed-to `RustBuffer` to the given value.
+ */
+ 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)
+ }
+}
+
+// This is a helper for safely passing byte references into the rust code.
+// It's not actually used at the moment, because there aren't many things that you
+// can take a direct pointer to in the JVM, and if we're going to copy something
+// then we might as well copy it into a `RustBuffer`. But it's here for API
+// completeness.
+
+@Structure.FieldOrder("len", "data")
+open class ForeignBytes : Structure() {
+ @JvmField var len: Int = 0
+ @JvmField var data: Pointer? = null
+
+ class ByValue : ForeignBytes(), Structure.ByValue
+}
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
new file mode 100644
index 0000000000..b5c699ba3b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
@@ -0,0 +1,23 @@
+{%- let inner_type_name = inner_type|type_name %}
+
+public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_type_name }}>> {
+ override fun read(buf: ByteBuffer): List<{{ inner_type_name }}> {
+ val len = buf.getInt()
+ return List<{{ inner_type_name }}>(len) {
+ {{ inner_type|read_fn }}(buf)
+ }
+ }
+
+ override fun allocationSize(value: List<{{ inner_type_name }}>): Int {
+ val sizeForLength = 4
+ 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 {
+ {{ 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
new file mode 100644
index 0000000000..d3443215c1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
@@ -0,0 +1,45 @@
+public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
+ // Note: we don't inherit from FfiConverterRustBuffer, because we use a
+ // special encoding when lowering/lifting. We can use `RustBuffer.len` to
+ // store our length and avoid writing it out to the buffer.
+ override fun lift(value: RustBuffer.ByValue): String {
+ try {
+ val byteArr = ByteArray(value.len)
+ value.asByteBuffer()!!.get(byteArr)
+ return byteArr.toString(Charsets.UTF_8)
+ } finally {
+ RustBuffer.free(value)
+ }
+ }
+
+ override fun read(buf: ByteBuffer): String {
+ val len = buf.getInt()
+ val byteArr = ByteArray(len)
+ buf.get(byteArr)
+ return byteArr.toString(Charsets.UTF_8)
+ }
+
+ override fun lower(value: String): RustBuffer.ByValue {
+ val byteArr = value.toByteArray(Charsets.UTF_8)
+ // 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(byteArr.size)
+ rbuf.asByteBuffer()!!.put(byteArr)
+ return rbuf
+ }
+
+ // We aren't sure exactly how many bytes our string will be once it's UTF-8
+ // encoded. Allocate 3 bytes per unicode codepoint which will always be
+ // enough.
+ override fun allocationSize(value: String): Int {
+ val sizeForLength = 4
+ val sizeForString = value.length * 3
+ return sizeForLength + sizeForString
+ }
+
+ override fun write(value: String, buf: ByteBuffer) {
+ val byteArr = value.toByteArray(Charsets.UTF_8)
+ buf.putInt(byteArr.size)
+ buf.put(byteArr)
+ }
+}
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
new file mode 100644
index 0000000000..21069d7ce8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
@@ -0,0 +1,38 @@
+public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> {
+ override fun read(buf: ByteBuffer): java.time.Instant {
+ val seconds = buf.getLong()
+ // Type mismatch (should be u32) but we check for overflow/underflow below
+ val nanoseconds = buf.getInt().toLong()
+ if (nanoseconds < 0) {
+ throw java.time.DateTimeException("Instant nanoseconds exceed minimum or maximum supported by uniffi")
+ }
+ if (seconds >= 0) {
+ return java.time.Instant.EPOCH.plus(java.time.Duration.ofSeconds(seconds, nanoseconds))
+ } else {
+ return java.time.Instant.EPOCH.minus(java.time.Duration.ofSeconds(-seconds, nanoseconds))
+ }
+ }
+
+ // 8 bytes for seconds, 4 bytes for nanoseconds
+ override fun allocationSize(value: java.time.Instant) = 12
+
+ override fun write(value: java.time.Instant, buf: ByteBuffer) {
+ var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value)
+
+ var sign = 1
+ if (epochOffset.isNegative()) {
+ sign = -1
+ epochOffset = epochOffset.negated()
+ }
+
+ if (epochOffset.nano < 0) {
+ // Java docs provide guarantee that nano will always be positive, so this should be impossible
+ // See: https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html
+ throw IllegalArgumentException("Invalid timestamp, nano value must be non-negative")
+ }
+
+ buf.putLong(sign * epochOffset.seconds)
+ // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK
+ buf.putInt(epochOffset.nano)
+ }
+}
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
new file mode 100644
index 0000000000..180a64066a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
@@ -0,0 +1,17 @@
+{%- match func.throws_type() -%}
+{%- when Some with (throwable) %}
+@Throws({{ throwable|type_name }}::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 }} {
+ 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 %}
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
new file mode 100644
index 0000000000..068bc05de5
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
@@ -0,0 +1,94 @@
+{%- import "macros.kt" as kt %}
+
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+{%- let contains_object_references = ci.item_contains_object_references(type_) %}
+
+{#
+ # Map `Type` instances to an include statement for that type.
+ #
+ # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the
+ # Rust code.
+ #
+ # - When adding additional types here, make sure to also add a match arm to that function.
+ # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ #}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "BooleanHelper.kt" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8Helper.kt" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16Helper.kt" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32Helper.kt" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64Helper.kt" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8Helper.kt" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16Helper.kt" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32Helper.kt" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64Helper.kt" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32Helper.kt" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64Helper.kt" %}
+
+{%- when Type::String %}
+{%- include "StringHelper.kt" %}
+
+{%- when Type::Enum(name) %}
+{% include "EnumTemplate.kt" %}
+
+{%- when Type::Error(name) %}
+{% include "ErrorTemplate.kt" %}
+
+{%- when Type::Object(name) %}
+{% include "ObjectTemplate.kt" %}
+
+{%- when Type::Record(name) %}
+{% include "RecordTemplate.kt" %}
+
+{%- when Type::Optional(inner_type) %}
+{% include "OptionalTemplate.kt" %}
+
+{%- when Type::Sequence(inner_type) %}
+{% include "SequenceTemplate.kt" %}
+
+{%- when Type::Map(key_type, value_type) %}
+{% include "MapTemplate.kt" %}
+
+{%- when Type::CallbackInterface(name) %}
+{% include "CallbackInterfaceTemplate.kt" %}
+
+{%- when Type::Timestamp %}
+{% include "TimestampHelper.kt" %}
+
+{%- when Type::Duration %}
+{% include "DurationHelper.kt" %}
+
+{%- when Type::Custom { name, builtin } %}
+{% include "CustomTypeTemplate.kt" %}
+
+{%- when Type::External { crate_name, name } %}
+{% include "ExternalTypeTemplate.kt" %}
+
+{%- else %}
+{%- endmatch %}
+{%- endfor %}
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
new file mode 100644
index 0000000000..279a8fa91b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUShort: FfiConverter<UShort, Short> {
+ override fun lift(value: Short): UShort {
+ return value.toUShort()
+ }
+
+ override fun read(buf: ByteBuffer): UShort {
+ return lift(buf.getShort())
+ }
+
+ override fun lower(value: UShort): Short {
+ return value.toShort()
+ }
+
+ override fun allocationSize(value: UShort) = 2
+
+ 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
new file mode 100644
index 0000000000..da7b5b28d6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUInt: FfiConverter<UInt, Int> {
+ override fun lift(value: Int): UInt {
+ return value.toUInt()
+ }
+
+ override fun read(buf: ByteBuffer): UInt {
+ return lift(buf.getInt())
+ }
+
+ override fun lower(value: UInt): Int {
+ return value.toInt()
+ }
+
+ override fun allocationSize(value: UInt) = 4
+
+ 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
new file mode 100644
index 0000000000..44d27ad36b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterULong: FfiConverter<ULong, Long> {
+ override fun lift(value: Long): ULong {
+ return value.toULong()
+ }
+
+ override fun read(buf: ByteBuffer): ULong {
+ return lift(buf.getLong())
+ }
+
+ override fun lower(value: ULong): Long {
+ return value.toLong()
+ }
+
+ override fun allocationSize(value: ULong) = 8
+
+ 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
new file mode 100644
index 0000000000..b6d176603e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
@@ -0,0 +1,19 @@
+public object FfiConverterUByte: FfiConverter<UByte, Byte> {
+ override fun lift(value: Byte): UByte {
+ return value.toUByte()
+ }
+
+ override fun read(buf: ByteBuffer): UByte {
+ return lift(buf.get())
+ }
+
+ override fun lower(value: UByte): Byte {
+ return value.toByte()
+ }
+
+ override fun allocationSize(value: UByte) = 1
+
+ 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
new file mode 100644
index 0000000000..32182d5e45
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
@@ -0,0 +1,82 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call`
+#}
+
+{%- macro to_ffi_call(func) -%}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|type_name}})
+ {%- else %}
+ rustCall()
+ {%- endmatch %} { _status ->
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %},{% endif %} _status)
+}
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) %}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|type_name}})
+ {%- else %}
+ rustCall()
+ {%- endmatch %} { _status ->
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}(
+ {{- prefix }}, {% call _arg_list_ffi_call(func) %}{% if func.arguments().len() > 0 %}, {% endif %} _status)
+}
+{%- endmacro %}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg|lower_fn }}({{ arg.name()|var_name }})
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in kotlin declarations of methods, functions and constructors.
+// Note the var_name and type_name filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}: {{ arg|type_name -}}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|render_literal(arg) }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{% macro arg_list_protocol(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}: {{ arg|type_name -}}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+{#-
+// 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 -}},
+ {%- endfor %}
+ _uniffi_out_err: RustCallStatus
+{%- endmacro -%}
+
+// Macro for destroying fields
+{%- macro destroy_fields(member) %}
+ Disposable.destroy(
+ {%- for field in member.fields() %}
+ this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%}
+ {% endfor -%})
+{%- endmacro -%}
+
+{%- macro ffi_function_definition(func) %}
+fun {{ func.name()|fn_name }}(
+ {%- call arg_list_ffi_decl(func) %}
+){%- match func.return_type() -%}{%- when Some with (type_) %}: {{ type_|ffi_type_name }}{% when None %}: Unit{% endmatch %}
+{% 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
new file mode 100644
index 0000000000..ef42d8e616
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
@@ -0,0 +1,48 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+
+@file:Suppress("NAME_SHADOWING")
+
+package {{ config.package_name() }};
+
+// Common helper code.
+//
+// Ideally this would live in a separate .kt file where it can be unittested etc
+// in isolation, and perhaps even published as a re-useable package.
+//
+// However, it's important that the detils of how this helper code works (e.g. the
+// way that different builtin types are passed across the FFI) exactly match what's
+// expected by the Rust code on the other side of the interface. In practice right
+// now that means coming from the exact some version of `uniffi` that was used to
+// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin
+// helpers directly inline like we're doing here.
+
+import com.sun.jna.Library
+import com.sun.jna.Native
+import com.sun.jna.Pointer
+import com.sun.jna.Structure
+import com.sun.jna.ptr.ByReference
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+{%- for req in self.imports() %}
+{{ req.render() }}
+{%- endfor %}
+
+{% include "RustBufferTemplate.kt" %}
+{% include "FfiConverterTemplate.kt" %}
+{% include "Helpers.kt" %}
+
+// Contains loading, initialization code,
+// and the FFI Function declarations in a com.sun.jna.Library.
+{% include "NamespaceLibraryTemplate.kt" %}
+
+// Public interface members begin here.
+{{ type_helper_code }}
+
+{%- for func in ci.function_definitions() %}
+{%- include "TopLevelFunctionTemplate.kt" %}
+{%- endfor %}
+
+
+{% import "macros.kt" as kt %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
new file mode 100644
index 0000000000..e8172ed15b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
@@ -0,0 +1,107 @@
+/* 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 anyhow::{bail, Context, Result};
+use camino::{Utf8Path, Utf8PathBuf};
+use std::env;
+use std::process::Command;
+use uniffi_testing::UniFFITestHelper;
+
+/// Run Kotlin tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file);
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_fixture_library_to_out_dir")?;
+ generate_sources(&test_helper.cdylib_path()?, &out_dir, &test_helper)
+ .context("generate_sources")?;
+ let jar_file = build_jar(fixture_name, &out_dir).context("build_jar")?;
+
+ let status = Command::new("kotlinc")
+ .arg("-classpath")
+ .arg(calc_classpath(vec![&out_dir, &jar_file]))
+ // Enable runtime assertions, for easy testing etc.
+ .arg("-J-ea")
+ // Our test scripts should not produce any warnings.
+ .arg("-Werror")
+ .arg("-script")
+ .arg(script_path)
+ .spawn()
+ .context("Failed to spawn `kotlinc` to run Kotlin script")?
+ .wait()
+ .context("Failed to wait for `kotlinc` when running Kotlin script")?;
+ if !status.success() {
+ anyhow::bail!("running `kotlinc` failed")
+ }
+ Ok(())
+}
+
+fn generate_sources(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+) -> Result<()> {
+ for source in test_helper.get_compile_sources()? {
+ println!(
+ "Generating bindings: {:?} {:?} {:?}",
+ source.udl_path, source.config_path, out_dir
+ );
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["kotlin"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+ Ok(())
+}
+
+/// Generate kotlin bindings for the given namespace, then use the kotlin
+/// command-line tools to compile them into a .jar file.
+fn build_jar(fixture_name: &str, out_dir: &Utf8Path) -> Result<Utf8PathBuf> {
+ let mut jar_file = Utf8PathBuf::from(out_dir);
+ jar_file.push(format!("{}.jar", fixture_name));
+ let sources = glob::glob(out_dir.join("**/*.kt").as_str())?
+ .flatten()
+ .map(|p| String::from(p.to_string_lossy()))
+ .collect::<Vec<String>>();
+ if sources.is_empty() {
+ bail!("No kotlin sources found in {out_dir}")
+ }
+ println!("building jar from: {:?}", sources);
+
+ let status = Command::new("kotlinc")
+ // Our generated bindings should not produce any warnings; fail tests if they do.
+ .arg("-Werror")
+ .arg("-d")
+ .arg(&jar_file)
+ .arg("-classpath")
+ .arg(calc_classpath(vec![]))
+ .args(sources)
+ .spawn()
+ .context("Failed to spawn `kotlinc` to compile the bindings")?
+ .wait()
+ .context("Failed to wait for `kotlinc` when compiling the bindings")?;
+ if !status.success() {
+ bail!("running `kotlinc` failed")
+ }
+ Ok(jar_file)
+}
+
+fn calc_classpath(extra_paths: Vec<&Utf8Path>) -> String {
+ extra_paths
+ .into_iter()
+ .map(|p| p.to_string())
+ // Add the system classpath as a component, using the fact that env::var returns an Option,
+ // which implement Iterator
+ .chain(env::var("CLASSPATH"))
+ .collect::<Vec<String>>()
+ .join(":")
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/mod.rs
new file mode 100644
index 0000000000..10f6e5fe2c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/mod.rs
@@ -0,0 +1,121 @@
+/* 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/. */
+
+//! Generate foreign language bindings for a uniffi component.
+//!
+//! This module contains all the code for generating foreign language bindings,
+//! along with some helpers for executing foreign language scripts or tests.
+
+use anyhow::{bail, Result};
+use camino::Utf8Path;
+use serde::{Deserialize, Serialize};
+
+use crate::interface::ComponentInterface;
+use crate::MergeWith;
+
+pub mod kotlin;
+pub mod python;
+pub mod ruby;
+pub mod swift;
+
+/// Enumeration of all foreign language targets currently supported by this crate.
+///
+/// The functions in this module will delegate to a language-specific backend based
+/// on the provided `TargetLanguage`. For convenience of calling code we also provide
+/// a few `TryFrom` implementations to help guess the correct target language from
+/// e.g. a file extension of command-line argument.
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub enum TargetLanguage {
+ Kotlin,
+ Swift,
+ Python,
+ Ruby,
+}
+
+impl TryFrom<&str> for TargetLanguage {
+ type Error = anyhow::Error;
+ fn try_from(value: &str) -> Result<Self> {
+ Ok(match value.to_ascii_lowercase().as_str() {
+ "kotlin" | "kt" | "kts" => TargetLanguage::Kotlin,
+ "swift" => TargetLanguage::Swift,
+ "python" | "py" => TargetLanguage::Python,
+ "ruby" | "rb" => TargetLanguage::Ruby,
+ _ => bail!("Unknown or unsupported target language: \"{}\"", value),
+ })
+ }
+}
+
+impl TryFrom<&std::ffi::OsStr> for TargetLanguage {
+ type Error = anyhow::Error;
+ fn try_from(value: &std::ffi::OsStr) -> Result<Self> {
+ match value.to_str() {
+ None => bail!("Unreadable target language"),
+ Some(s) => s.try_into(),
+ }
+ }
+}
+
+impl TryFrom<String> for TargetLanguage {
+ type Error = anyhow::Error;
+ fn try_from(value: String) -> Result<Self> {
+ TryFrom::try_from(value.as_str())
+ }
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ #[serde(default)]
+ kotlin: kotlin::Config,
+ #[serde(default)]
+ swift: swift::Config,
+ #[serde(default)]
+ python: python::Config,
+ #[serde(default)]
+ ruby: ruby::Config,
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ kotlin: ci.into(),
+ swift: ci.into(),
+ python: ci.into(),
+ ruby: ci.into(),
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ kotlin: self.kotlin.merge_with(&other.kotlin),
+ swift: self.swift.merge_with(&other.swift),
+ python: self.python.merge_with(&other.python),
+ ruby: self.ruby.merge_with(&other.ruby),
+ }
+ }
+}
+
+/// Generate foreign language bindings from a compiled `uniffi` library.
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ language: TargetLanguage,
+ try_format_code: bool,
+) -> Result<()> {
+ match language {
+ TargetLanguage::Kotlin => {
+ kotlin::write_bindings(&config.kotlin, ci, out_dir, try_format_code)?
+ }
+ TargetLanguage::Swift => {
+ swift::write_bindings(&config.swift, ci, out_dir, try_format_code)?
+ }
+ TargetLanguage::Python => {
+ python::write_bindings(&config.python, ci, out_dir, try_format_code)?
+ }
+ TargetLanguage::Ruby => ruby::write_bindings(&config.ruby, ci, out_dir, try_format_code)?,
+ }
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs
new file mode 100644
index 0000000000..9359fbaeae
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs
@@ -0,0 +1,33 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct CallbackInterfaceCodeType {
+ id: String,
+}
+
+impl CallbackInterfaceCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for CallbackInterfaceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("CallbackInterface{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
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
new file mode 100644
index 0000000000..80b1da040d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
@@ -0,0 +1,122 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal, TypeIdentifier};
+
+pub struct OptionalCodeType {
+ inner: TypeIdentifier,
+}
+
+impl OptionalCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for OptionalCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.find(&self.inner).type_label(oracle)
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Optional{}",
+ oracle.find(&self.inner).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::Null => "None".into(),
+ _ => oracle.find(&self.inner).literal(oracle, literal),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "(None if {} is None else {})",
+ nm,
+ oracle.find(&self.inner).coerce(oracle, nm)
+ )
+ }
+}
+
+pub struct SequenceCodeType {
+ inner: TypeIdentifier,
+}
+
+impl SequenceCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for SequenceCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "list".to_string()
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Sequence{}",
+ oracle.find(&self.inner).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptySequence => "[]".into(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "list({} for x in {})",
+ oracle.find(&self.inner).coerce(oracle, "x"),
+ nm
+ )
+ }
+}
+
+pub struct MapCodeType {
+ key: TypeIdentifier,
+ value: TypeIdentifier,
+}
+
+impl MapCodeType {
+ pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self {
+ Self { key, value }
+ }
+}
+
+impl CodeType for MapCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "dict".to_string()
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Map{}{}",
+ oracle.find(&self.key).canonical_name(oracle),
+ oracle.find(&self.value).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptyMap => "{}".into(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "dict(({}, {}) for (k, v) in {}.items())",
+ self.key.coerce(oracle, "k"),
+ self.value.coerce(oracle, "v"),
+ nm
+ )
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs
new file mode 100644
index 0000000000..d53a946947
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs
@@ -0,0 +1,29 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct CustomCodeType {
+ name: String,
+}
+
+impl CustomCodeType {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+impl CodeType for CustomCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs
new file mode 100644
index 0000000000..9a86076770
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs
@@ -0,0 +1,41 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct EnumCodeType {
+ id: String,
+}
+
+impl EnumCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for EnumCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ if let Literal::Enum(v, _) = literal {
+ format!(
+ "{}.{}",
+ self.type_label(oracle),
+ oracle.enum_variant_name(v)
+ )
+ } else {
+ unreachable!();
+ }
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs
new file mode 100644
index 0000000000..f1567281f2
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs
@@ -0,0 +1,33 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct ErrorCodeType {
+ id: String,
+}
+
+impl ErrorCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ErrorCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
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
new file mode 100644
index 0000000000..1739d84cec
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
@@ -0,0 +1,29 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct ExternalCodeType {
+ name: String,
+}
+
+impl ExternalCodeType {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+impl CodeType for ExternalCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.into()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs
new file mode 100644
index 0000000000..c885428bc3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs
@@ -0,0 +1,36 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+use paste::paste;
+
+macro_rules! impl_code_type_for_miscellany {
+ ($T:ty, $canonical_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("{}", $canonical_name)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("{}", $canonical_name)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!()
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_miscellany!(TimestampCodeType, "Timestamp");
+
+impl_code_type_for_miscellany!(DurationCodeType, "Duration");
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
new file mode 100644
index 0000000000..0b2218402a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
@@ -0,0 +1,448 @@
+/* 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 anyhow::{Context, Result};
+use askama::Template;
+use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+use std::borrow::Borrow;
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap, HashSet};
+
+use crate::backend::{CodeOracle, CodeType, TemplateExpression, TypeIdentifier};
+use crate::interface::*;
+use crate::MergeWith;
+
+mod callback_interface;
+mod compounds;
+mod custom;
+mod enum_;
+mod error;
+mod external;
+mod miscellany;
+mod object;
+mod primitives;
+mod record;
+
+// Taken from Python's `keyword.py` module.
+static KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| {
+ let kwlist = vec![
+ "False",
+ "None",
+ "True",
+ "__peg_parser__",
+ "and",
+ "as",
+ "assert",
+ "async",
+ "await",
+ "break",
+ "class",
+ "continue",
+ "def",
+ "del",
+ "elif",
+ "else",
+ "except",
+ "finally",
+ "for",
+ "from",
+ "global",
+ "if",
+ "import",
+ "in",
+ "is",
+ "lambda",
+ "nonlocal",
+ "not",
+ "or",
+ "pass",
+ "raise",
+ "return",
+ "try",
+ "while",
+ "with",
+ "yield",
+ ];
+ HashSet::from_iter(kwlist.into_iter().map(|s| s.to_string()))
+});
+
+// Config options to customize the generated python.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ cdylib_name: Option<String>,
+ #[serde(default)]
+ custom_types: HashMap<String, CustomTypeConfig>,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct CustomTypeConfig {
+ // This `CustomTypeConfig` doesn't have a `type_name` like the others -- which is why we have
+ // separate structs rather than a shared one.
+ imports: Option<Vec<String>>,
+ into_custom: TemplateExpression,
+ from_custom: TemplateExpression,
+}
+
+impl Config {
+ pub fn cdylib_name(&self) -> String {
+ if let Some(cdylib_name) = &self.cdylib_name {
+ cdylib_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ custom_types: HashMap::new(),
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ custom_types: self.custom_types.merge_with(&other.custom_types),
+ }
+ }
+}
+
+// Generate python bindings for the given ComponentInterface, as a string.
+pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
+ PythonWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render python bindings")
+}
+
+/// A struct to record a Python import statement.
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub enum ImportRequirement {
+ /// A simple module import.
+ Module { mod_name: String },
+ /// A single symbol from a module.
+ Symbol {
+ mod_name: String,
+ symbol_name: String,
+ },
+ /// A single symbol from a module with the specified local name.
+ SymbolAs {
+ mod_name: String,
+ symbol_name: String,
+ as_name: String,
+ },
+}
+
+impl ImportRequirement {
+ /// Render the Python import statement.
+ fn render(&self) -> String {
+ match &self {
+ ImportRequirement::Module { mod_name } => format!("import {mod_name}"),
+ ImportRequirement::Symbol {
+ mod_name,
+ symbol_name,
+ } => format!("from {mod_name} import {symbol_name}"),
+ ImportRequirement::SymbolAs {
+ mod_name,
+ symbol_name,
+ as_name,
+ } => format!("from {mod_name} import {symbol_name} as {as_name}"),
+ }
+ }
+}
+
+/// Renders Python helper code for all types
+///
+/// This template is a bit different than others in that it stores internal state from the render
+/// process. Make sure to only call `render()` once.
+#[derive(Template)]
+#[template(syntax = "py", escape = "none", path = "Types.py")]
+pub struct TypeRenderer<'a> {
+ python_config: &'a Config,
+ ci: &'a ComponentInterface,
+ // Track included modules for the `include_once()` macro
+ include_once_names: RefCell<HashSet<String>>,
+ // Track imports added with the `add_import()` macro
+ imports: RefCell<BTreeSet<ImportRequirement>>,
+}
+
+impl<'a> TypeRenderer<'a> {
+ fn new(python_config: &'a Config, ci: &'a ComponentInterface) -> Self {
+ Self {
+ python_config,
+ ci,
+ include_once_names: RefCell::new(HashSet::new()),
+ imports: RefCell::new(BTreeSet::new()),
+ }
+ }
+
+ // The following methods are used by the `Types.py` macros.
+
+ // Helper for the including a template, but only once.
+ //
+ // The first time this is called with a name it will return true, indicating that we should
+ // include the template. Subsequent calls will return false.
+ fn include_once_check(&self, name: &str) -> bool {
+ self.include_once_names
+ .borrow_mut()
+ .insert(name.to_string())
+ }
+
+ // Helper to add an import statement
+ //
+ // Call this inside your template to cause an import statement to be added at the top of the
+ // file. Imports will be sorted and de-deuped.
+ //
+ // Returns an empty string so that it can be used inside an askama `{{ }}` block.
+ fn add_import(&self, name: &str) -> &str {
+ self.imports.borrow_mut().insert(ImportRequirement::Module {
+ mod_name: name.to_owned(),
+ });
+ ""
+ }
+
+ // Like add_import, but arranges for `from module import name`.
+ fn add_import_of(&self, mod_name: &str, name: &str) -> &str {
+ self.imports.borrow_mut().insert(ImportRequirement::Symbol {
+ mod_name: mod_name.to_owned(),
+ symbol_name: name.to_owned(),
+ });
+ ""
+ }
+
+ // Like add_import, but arranges for `from module import name as other`.
+ fn add_import_of_as(&self, mod_name: &str, symbol_name: &str, as_name: &str) -> &str {
+ self.imports
+ .borrow_mut()
+ .insert(ImportRequirement::SymbolAs {
+ mod_name: mod_name.to_owned(),
+ symbol_name: symbol_name.to_owned(),
+ as_name: as_name.to_owned(),
+ });
+ ""
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "py", escape = "none", path = "wrapper.py")]
+pub struct PythonWrapper<'a> {
+ ci: &'a ComponentInterface,
+ config: Config,
+ type_helper_code: String,
+ type_imports: BTreeSet<ImportRequirement>,
+}
+impl<'a> PythonWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ let type_renderer = TypeRenderer::new(&config, ci);
+ let type_helper_code = type_renderer.render().unwrap();
+ let type_imports = type_renderer.imports.into_inner();
+ Self {
+ config,
+ ci,
+ type_helper_code,
+ type_imports,
+ }
+ }
+
+ pub fn imports(&self) -> Vec<ImportRequirement> {
+ self.type_imports.iter().cloned().collect()
+ }
+}
+
+fn fixup_keyword(name: String) -> String {
+ if KEYWORDS.contains(&name) {
+ format!("_{name}")
+ } else {
+ name
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct PythonCodeOracle;
+
+impl PythonCodeOracle {
+ // Map `Type` instances to a `Box<dyn CodeType>` for that type.
+ //
+ // There is a companion match in `templates/Types.py` which performs a similar function for the
+ // template code.
+ //
+ // - When adding additional types here, make sure to also add a match arm to the `Types.py` template.
+ // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+ match type_ {
+ Type::UInt8 => Box::new(primitives::UInt8CodeType),
+ Type::Int8 => Box::new(primitives::Int8CodeType),
+ Type::UInt16 => Box::new(primitives::UInt16CodeType),
+ Type::Int16 => Box::new(primitives::Int16CodeType),
+ Type::UInt32 => Box::new(primitives::UInt32CodeType),
+ Type::Int32 => Box::new(primitives::Int32CodeType),
+ Type::UInt64 => Box::new(primitives::UInt64CodeType),
+ Type::Int64 => Box::new(primitives::Int64CodeType),
+ Type::Float32 => Box::new(primitives::Float32CodeType),
+ Type::Float64 => Box::new(primitives::Float64CodeType),
+ Type::Boolean => Box::new(primitives::BooleanCodeType),
+ Type::String => Box::new(primitives::StringCodeType),
+
+ Type::Timestamp => Box::new(miscellany::TimestampCodeType),
+ Type::Duration => Box::new(miscellany::DurationCodeType),
+
+ Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+ Type::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+ Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
+ Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
+ Type::CallbackInterface(id) => {
+ Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
+ }
+
+ Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)),
+ Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)),
+ Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)),
+ Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
+ Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling create_code_type")
+ }
+ }
+ }
+}
+
+impl CodeOracle for PythonCodeOracle {
+ fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+ self.create_code_type(type_.clone())
+ }
+
+ /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Python rendering of a function name.
+ fn fn_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of a variable name.
+ fn var_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of an individual enum variant.
+ fn enum_variant_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_shouty_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of an exception name
+ /// This replaces "Error" at the end of the name with "Exception".
+ fn error_name(&self, nm: &str) -> String {
+ let name = fixup_keyword(self.class_name(nm));
+ match name.strip_suffix("Error") {
+ None => name,
+ Some(stripped) => format!("{stripped}Exception"),
+ }
+ }
+
+ 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(),
+ FfiType::Int16 => "ctypes.c_int16".to_string(),
+ FfiType::UInt16 => "ctypes.c_uint16".to_string(),
+ FfiType::Int32 => "ctypes.c_int32".to_string(),
+ FfiType::UInt32 => "ctypes.c_uint32".to_string(),
+ FfiType::Int64 => "ctypes.c_int64".to_string(),
+ FfiType::UInt64 => "ctypes.c_uint64".to_string(),
+ FfiType::Float32 => "ctypes.c_float".to_string(),
+ FfiType::Float64 => "ctypes.c_double".to_string(),
+ FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
+ FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
+ Some(suffix) => format!("RustBuffer{}", suffix),
+ None => "RustBuffer".to_string(),
+ },
+ FfiType::ForeignBytes => "ForeignBytes".to_string(),
+ FfiType::ForeignCallback => "FOREIGN_CALLBACK_T".to_string(),
+ }
+ }
+}
+
+pub mod filters {
+ use super::*;
+
+ fn oracle() -> &'static PythonCodeOracle {
+ &PythonCodeOracle
+ }
+
+ pub fn type_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.type_label(oracle))
+ }
+
+ pub fn ffi_converter_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.ffi_converter_name(oracle))
+ }
+
+ pub fn canonical_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.canonical_name(oracle))
+ }
+
+ pub fn lift_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lift", ffi_converter_name(codetype)?))
+ }
+
+ pub fn lower_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lower", ffi_converter_name(codetype)?))
+ }
+
+ pub fn read_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.read", ffi_converter_name(codetype)?))
+ }
+
+ pub fn write_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.write", ffi_converter_name(codetype)?))
+ }
+
+ pub fn literal_py(
+ literal: &Literal,
+ codetype: &impl CodeType,
+ ) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.literal(oracle, literal))
+ }
+
+ /// Get the Python syntax for representing a given low-level `FfiType`.
+ pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_type_label(type_))
+ }
+
+ /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
+ pub fn class_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().class_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of a function name.
+ pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().fn_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of a variable name.
+ pub fn var_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().var_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of an individual enum variant.
+ pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().enum_variant_name(nm))
+ }
+
+ pub fn coerce_py(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(oracle.find(type_).coerce(oracle, nm))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs
new file mode 100644
index 0000000000..7744865ec0
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs
@@ -0,0 +1,33 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct ObjectCodeType {
+ id: String,
+}
+
+impl ObjectCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ObjectCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs
new file mode 100644
index 0000000000..de7918fb16
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs
@@ -0,0 +1,69 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+use crate::interface::Radix;
+use paste::paste;
+
+fn render_literal(_oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::Boolean(v) => {
+ if *v {
+ "True".into()
+ } else {
+ "False".into()
+ }
+ }
+ Literal::String(s) => format!("\"{s}\""),
+ // https://docs.python.org/3/reference/lexical_analysis.html#integer-literals
+ Literal::Int(i, radix, _) => match radix {
+ Radix::Octal => format!("int(0o{i:o})"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::UInt(i, radix, _) => match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::Float(string, _type_) => string.clone(),
+
+ _ => unreachable!("Literal"),
+ }
+}
+
+macro_rules! impl_code_type_for_primitive {
+ ($T:ty, $class_name:literal, $coerce_code:expr) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, &literal)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!($coerce_code, nm)
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_primitive!(BooleanCodeType, "Bool", "bool({})");
+impl_code_type_for_primitive!(StringCodeType, "String", "{}");
+impl_code_type_for_primitive!(Int8CodeType, "Int8", "int({})");
+impl_code_type_for_primitive!(Int16CodeType, "Int16", "int({})");
+impl_code_type_for_primitive!(Int32CodeType, "Int32", "int({})");
+impl_code_type_for_primitive!(Int64CodeType, "Int64", "int({})");
+impl_code_type_for_primitive!(UInt8CodeType, "UInt8", "int({})");
+impl_code_type_for_primitive!(UInt16CodeType, "UInt16", "int({})");
+impl_code_type_for_primitive!(UInt32CodeType, "UInt32", "int({})");
+impl_code_type_for_primitive!(UInt64CodeType, "UInt64", "int({})");
+impl_code_type_for_primitive!(Float32CodeType, "Float", "float({})");
+impl_code_type_for_primitive!(Float64CodeType, "Double", "float({})");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs
new file mode 100644
index 0000000000..1dba8e49fe
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs
@@ -0,0 +1,33 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct RecordCodeType {
+ id: String,
+}
+
+impl RecordCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for RecordCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs
new file mode 100644
index 0000000000..3a1cfaa78d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs
@@ -0,0 +1,40 @@
+/* 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 std::{io::Write, process::Command};
+
+use anyhow::Result;
+use camino::Utf8Path;
+use fs_err::File;
+
+pub mod gen_python;
+mod test;
+
+use super::super::interface::ComponentInterface;
+pub use gen_python::{generate_python_bindings, Config};
+pub use test::run_test;
+
+// Generate python bindings for the given ComponentInterface, in the given output directory.
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let py_file = out_dir.join(format!("{}.py", ci.namespace()));
+ let mut f = File::create(&py_file)?;
+ write!(f, "{}", generate_python_bindings(config, ci)?)?;
+
+ if try_format_code {
+ if let Err(e) = Command::new("yapf").arg(&py_file).output() {
+ println!(
+ "Warning: Unable to auto-format {} using yapf: {:?}",
+ py_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+
+ Ok(())
+}
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
new file mode 100644
index 0000000000..d7b64b91f7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
@@ -0,0 +1,16 @@
+class FfiConverterBool:
+ @classmethod
+ def read(cls, buf):
+ return cls.lift(buf.readU8())
+
+ @classmethod
+ def write(cls, value, buf):
+ buf.writeU8(cls.lower(value))
+
+ @staticmethod
+ def lift(value):
+ return int(value) != 0
+
+ @staticmethod
+ def lower(value):
+ return 1 if value else 0
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
new file mode 100644
index 0000000000..e8a0ec56d9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
@@ -0,0 +1,73 @@
+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
+
+class FfiConverterCallbackInterface:
+ _handle_map = ConcurrentHandleMap()
+
+ def __init__(self, cb):
+ self._foreign_callback = cb
+
+ def drop(self, handle):
+ self.__class__._handle_map.remove(handle)
+
+ @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
+
+ @classmethod
+ def read(cls, buf):
+ handle = buf.readU64()
+ cls.lift(handle)
+
+ @classmethod
+ def lower(cls, cb):
+ handle = cls._handle_map.insert(cb)
+ return handle
+
+ @classmethod
+ def write(cls, cb, buf):
+ buf.writeU64(cls.lower(cb))
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
new file mode 100644
index 0000000000..cf4411b8a0
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
@@ -0,0 +1,104 @@
+{%- let cbi = ci.get_callback_interface_definition(id).unwrap() %}
+{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
+
+{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %}
+
+# Declaration and FfiConverters for {{ type_name }} Callback Interface
+
+class {{ type_name }}:
+ {% for meth in cbi.methods() -%}
+ def {{ meth.name()|fn_name }}({% call py::arg_list_decl(meth) %}):
+ raise NotImplementedError
+
+ {% endfor %}
+
+def py_{{ foreign_callback }}(handle, method, args, buf_ptr):
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
+ def {{ method_name }}(python_callback, args):
+ {#- Unpacking args from the RustBuffer #}
+ {%- if meth.arguments().len() != 0 -%}
+ {#- Calling the concrete callback object #}
+ with args.consumeWithStream() as buf:
+ rval = python_callback.{{ meth.name()|fn_name }}(
+ {% for arg in meth.arguments() -%}
+ {{ arg|read_fn }}(buf)
+ {%- if !loop.last %}, {% endif %}
+ {% endfor -%}
+ )
+ {% else %}
+ rval = python_callback.{{ meth.name()|fn_name }}()
+ {% endif -%}
+
+ {#- Packing up the return value into a RustBuffer #}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ with RustBuffer.allocWithBuilder() as builder:
+ {{ return_type|write_fn }}(rval, builder)
+ return builder.finalize()
+ {%- else -%}
+ return RustBuffer.alloc(0)
+ {% 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)
+ # No return value.
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 0
+
+ {% 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/src/ffi/foreigncallbacks.rs` for details
+ try:
+ {%- match meth.throws_type() %}
+ {%- when Some(err) %}
+ try:
+ # Successful return
+ buf_ptr[0] = {{ method_name }}(cb, args)
+ return 1
+ except {{ err|type_name }} as e:
+ # Catch errors declared in the UDL file
+ with RustBuffer.allocWithBuilder() as builder:
+ {{ err|write_fn }}(e, builder)
+ buf_ptr[0] = builder.finalize()
+ return -2
+ {%- else %}
+ # Successful return
+ buf_ptr[0] = {{ method_name }}(cb, args)
+ return 1
+ {%- endmatch %}
+ 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 -1
+ {% 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/src/ffi/foreigncallbacks.rs`
+ return -1
+
+# 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 }} = FOREIGN_CALLBACK_T(py_{{ foreign_callback }})
+
+# The FfiConverter which transforms the Callbacks in to Handles to pass to Rust.
+rust_call(lambda err: _UniFFILib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err))
+{{ ffi_converter_name }} = FfiConverterCallbackInterface({{ foreign_callback }})
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
new file mode 100644
index 0000000000..9ce6a21ced
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
@@ -0,0 +1,52 @@
+{%- match python_config.custom_types.get(name.as_str()) %}
+{% when None %}
+{#- No custom type config, just forward all methods to our builtin type #}
+class FfiConverterType{{ name }}:
+ @staticmethod
+ def write(value, buf):
+ {{ builtin|ffi_converter_name }}.write(value, buf)
+
+ @staticmethod
+ def read(buf):
+ return {{ builtin|ffi_converter_name }}.read(buf)
+
+ @staticmethod
+ def lift(value):
+ return {{ builtin|ffi_converter_name }}.lift(value)
+
+ @staticmethod
+ def lower(value):
+ return {{ builtin|ffi_converter_name }}.lower(value)
+
+{%- when Some(config) %}
+
+{%- match config.imports %}
+{%- when Some(imports) %}
+{%- for import_name in imports %}
+{{ self.add_import(import_name) }}
+{%- endfor %}
+{%- else %}
+{%- endmatch %}
+
+{#- Custom type config supplied, use it to convert the builtin type #}
+class FfiConverterType{{ name }}:
+ @staticmethod
+ def write(value, buf):
+ builtin_value = {{ config.from_custom.render("value") }}
+ {{ builtin|write_fn }}(builtin_value, buf)
+
+ @staticmethod
+ def read(buf):
+ builtin_value = {{ builtin|read_fn }}(buf)
+ return {{ config.into_custom.render("builtin_value") }}
+
+ @staticmethod
+ def lift(value):
+ builtin_value = {{ builtin|lift_fn }}(value)
+ return {{ config.into_custom.render("builtin_value") }}
+
+ @staticmethod
+ def lower(value):
+ builtin_value = {{ config.from_custom.render("value") }}
+ return {{ builtin|lower_fn }}(builtin_value)
+{%- endmatch %}
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
new file mode 100644
index 0000000000..16fdd986f3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
@@ -0,0 +1,19 @@
+# The Duration type.
+# There is a loss of precision when converting from Rust durations,
+# which are accurate to the nanosecond,
+# to Python durations, which are only accurate to the microsecond.
+class FfiConverterDuration(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ seconds = buf.readU64()
+ microseconds = buf.readU32() / 1.0e3
+ return datetime.timedelta(seconds=seconds, microseconds=microseconds)
+
+ @staticmethod
+ def write(value, buf):
+ seconds = value.seconds + value.days * 24 * 3600
+ nanoseconds = value.microseconds * 1000
+ if seconds < 0:
+ raise ValueError("Invalid duration, must be non-negative")
+ buf.writeI64(seconds)
+ buf.writeU32(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
new file mode 100644
index 0000000000..5c366d8855
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
@@ -0,0 +1,93 @@
+{#
+# Python has a built-in `enum` module which is nice to use, but doesn't support
+# variants with associated data. So, we switch here, and generate a stdlib `enum`
+# when none of the variants have associated data, or a generic nested-class
+# construct when they do.
+#}
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+{% if e.is_flat() %}
+
+class {{ type_name }}(enum.Enum):
+ {% for variant in e.variants() -%}
+ {{ variant.name()|enum_variant_py }} = {{ loop.index }}
+ {% endfor %}
+{% else %}
+
+class {{ type_name }}:
+ 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 }}(object):
+ def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}):
+ {% if variant.has_fields() %}
+ {%- for field in variant.fields() %}
+ self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
+ {%- endfor %}
+ {% else %}
+ pass
+ {% 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 %})
+
+ def __eq__(self, other):
+ if not other.is_{{ variant.name()|var_name }}():
+ return False
+ {%- for field in variant.fields() %}
+ if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}:
+ return False
+ {%- endfor %}
+ return True
+ {% endfor %}
+
+ # For each variant, we have an `is_NAME` method for easily checking
+ # whether an instance is that variant.
+ {% for variant in e.variants() -%}
+ def is_{{ variant.name()|var_name }}(self):
+ return isinstance(self, {{ type_name }}.{{ variant.name()|enum_variant_py }})
+ {% endfor %}
+
+# Now, a little trick - we make each nested variant class be a subclass of the main
+# enum class, so that method calls and instance checks etc will work intuitively.
+# We might be able to do this a little more neatly with a metaclass, but this'll do.
+{% for variant in e.variants() -%}
+{{ type_name }}.{{ variant.name()|enum_variant_py }} = type("{{ type_name }}.{{ variant.name()|enum_variant_py }}", ({{ type_name }}.{{variant.name()|enum_variant_py}}, {{ type_name }},), {})
+{% endfor %}
+
+{% endif %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ variant = buf.readI32()
+
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}:
+ {%- if e.is_flat() %}
+ return {{ type_name }}.{{variant.name()|enum_variant_py}}
+ {%- else %}
+ return {{ type_name }}.{{variant.name()|enum_variant_py}}(
+ {%- for field in variant.fields() %}
+ {{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+ {%- endif %}
+ {%- endfor %}
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ def write(value, buf):
+ {%- for variant in e.variants() %}
+ {%- if e.is_flat() %}
+ if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
+ buf.writeI32({{ loop.index }})
+ {%- else %}
+ if value.is_{{ variant.name()|var_name }}():
+ buf.writeI32({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- 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
new file mode 100644
index 0000000000..305ae88618
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
@@ -0,0 +1,75 @@
+{%- let e = ci.get_error_definition(name).unwrap() %}
+
+# {{ type_name }}
+# We want to define each variant as a nested class that's also a subclass,
+# which is tricky in Python. To accomplish this we're going to create each
+# class separated, then manually add the child classes to the base class's
+# __dict__. All of this happens in dummy class to avoid polluting the module
+# namespace.
+class UniFFIExceptionTmpNamespace:
+ class {{ type_name }}(Exception):
+ pass
+ {% for variant in e.variants() %}
+ {%- let variant_type_name = variant.name()|class_name %}
+
+ {%- if e.is_flat() %}
+ class {{ variant_type_name }}({{ type_name }}):
+ def __str__(self):
+ return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(super().__str__()))
+ {%- else %}
+ class {{ variant_type_name }}({{ type_name }}):
+ def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}):
+ {%- if variant.has_fields() %}
+ {%- for field in variant.fields() %}
+ self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
+ {%- endfor %}
+ {%- else %}
+ pass
+ {%- endif %}
+
+ def __str__(self):
+ {%- if variant.has_fields() %}
+ field_parts = [
+ {%- for field in variant.fields() %}
+ '{{ field.name()|var_name }}={!r}'.format(self.{{ field.name()|var_name }}),
+ {%- endfor %}
+ ]
+ return "{{ type_name }}.{{ variant_type_name }}({})".format(', '.join(field_parts))
+ {%- else %}
+ return "{{ type_name }}.{{ variant_type_name }}()"
+ {%- endif %}
+ {%- endif %}
+
+ {{ type_name }}.{{ variant_type_name }} = {{ variant_type_name }}
+ {%- endfor %}
+{{ type_name }} = UniFFIExceptionTmpNamespace.{{ type_name }}
+del UniFFIExceptionTmpNamespace
+
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ variant = buf.readI32()
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}:
+ return {{ type_name }}.{{ variant.name()|class_name }}(
+ {%- if e.is_flat() %}
+ {{ Type::String.borrow()|read_fn }}(buf),
+ {%- else %}
+ {%- for field in variant.fields() %}
+ {{ field.name()|var_name }}={{ field|read_fn }}(buf),
+ {%- endfor %}
+ {%- endif %}
+ )
+ {%- endfor %}
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ @staticmethod
+ def write(value, buf):
+ {%- for variant in e.variants() %}
+ if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}):
+ buf.writeI32({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ {%- endfor %}
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
new file mode 100644
index 0000000000..a5f2584c62
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
@@ -0,0 +1,7 @@
+{%- let mod_name = crate_name|fn_name %}
+
+{%- let ffi_converter_name = "FfiConverterType{}"|format(name) %}
+{{ self.add_import_of(mod_name, ffi_converter_name) }}
+
+{%- let rustbuffer_local_name = "RustBuffer{}"|format(name) %}
+{{ self.add_import_of_as(mod_name, "RustBuffer", 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
new file mode 100644
index 0000000000..e5783dd158
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterFloat(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readFloat()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeFloat(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
new file mode 100644
index 0000000000..57c2131eb8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterDouble(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readDouble()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeDouble(value)
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
new file mode 100644
index 0000000000..fbce501981
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
@@ -0,0 +1,67 @@
+# 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.
+
+class InternalError(Exception):
+ pass
+
+class RustCallStatus(ctypes.Structure):
+ """
+ Error runtime.
+ """
+ _fields_ = [
+ ("code", ctypes.c_int8),
+ ("error_buf", RustBuffer),
+ ]
+
+ # These match the values from the uniffi::rustcalls module
+ CALL_SUCCESS = 0
+ CALL_ERROR = 1
+ CALL_PANIC = 2
+
+ def __str__(self):
+ if self.code == RustCallStatus.CALL_SUCCESS:
+ return "RustCallStatus(CALL_SUCCESS)"
+ elif self.code == RustCallStatus.CALL_ERROR:
+ return "RustCallStatus(CALL_ERROR)"
+ elif self.code == RustCallStatus.CALL_PANIC:
+ return "RustCallStatus(CALL_PANIC)"
+ else:
+ return "RustCallStatus(<invalid code>)"
+
+def rust_call(fn, *args):
+ # Call a rust function
+ return rust_call_with_error(None, fn, *args)
+
+def rust_call_with_error(error_ffi_converter, fn, *args):
+ # Call a rust function and handle any errors
+ #
+ # 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 FfiConverter for the error class that corresponds to the result.
+ call_status = RustCallStatus(code=RustCallStatus.CALL_SUCCESS, error_buf=RustBuffer(0, 0, None))
+
+ args_with_error = args + (ctypes.byref(call_status),)
+ result = fn(*args_with_error)
+ if call_status.code == RustCallStatus.CALL_SUCCESS:
+ return result
+ elif call_status.code == RustCallStatus.CALL_ERROR:
+ if error_ffi_converter is None:
+ call_status.error_buf.free()
+ 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 == RustCallStatus.CALL_PANIC:
+ # 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.
+ if call_status.error_buf.len > 0:
+ msg = FfiConverterString.lift(call_status.error_buf)
+ else:
+ msg = "Unknown rust panic"
+ raise InternalError(msg)
+ else:
+ raise InternalError("Invalid RustCallStatus code: {}".format(
+ call_status.code))
+
+# A function pointer for a callback as defined by UniFFI.
+# Rust definition `fn(handle: u64, method: u32, args: RustBuffer, buf_ptr: *mut RustBuffer) -> int`
+FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, RustBuffer, ctypes.POINTER(RustBuffer))
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
new file mode 100644
index 0000000000..b3d9602746
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt16(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI16()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI16(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
new file mode 100644
index 0000000000..47a54d8c4a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt32(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI32()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI32(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
new file mode 100644
index 0000000000..471b9967ce
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt64(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI64()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI64(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
new file mode 100644
index 0000000000..524fea7ea9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt8(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI8()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI8(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
new file mode 100644
index 0000000000..7f42daa7a9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
@@ -0,0 +1,27 @@
+{%- let key_ffi_converter = key_type|ffi_converter_name %}
+{%- let value_ffi_converter = value_type|ffi_converter_name %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, items, buf):
+ buf.writeI32(len(items))
+ for (key, value) in items.items():
+ {{ key_ffi_converter }}.write(key, buf)
+ {{ value_ffi_converter }}.write(value, buf)
+
+ @classmethod
+ def read(cls, buf):
+ count = buf.readI32()
+ if count < 0:
+ raise InternalError("Unexpected negative map size")
+
+ # It would be nice to use a dict comprehension,
+ # but in Python 3.7 and before the evaluation order is not according to spec,
+ # so we we're reading the value before the key.
+ # This loop makes the order explicit: first reading the key, then the value.
+ d = {}
+ for i in range(count):
+ key = {{ key_ffi_converter }}.read(buf)
+ val = {{ value_ffi_converter }}.read(buf)
+ d[key] = val
+ return d
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
new file mode 100644
index 0000000000..4867ecb9b4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
@@ -0,0 +1,39 @@
+# This is how we find and load the dynamic library provided by the component.
+# For now we just look it up by name.
+#
+# XXX TODO: This will probably grow some magic for resolving megazording in future.
+# E.g. we might start by looking for the named component in `libuniffi.so` and if
+# that fails, fall back to loading it separately from `lib${componentName}.so`.
+
+from pathlib import Path
+
+def loadIndirect():
+ if sys.platform == "darwin":
+ libname = "lib{}.dylib"
+ elif sys.platform.startswith("win"):
+ # As of python3.8, ctypes does not seem to search $PATH when loading DLLs.
+ # We could use `os.add_dll_directory` to configure the search path, but
+ # it doesn't feel right to mess with application-wide settings. Let's
+ # assume that the `.dll` is next to the `.py` file and load by full path.
+ libname = os.path.join(
+ os.path.dirname(__file__),
+ "{}.dll",
+ )
+ else:
+ # Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos
+ libname = "lib{}.so"
+
+ lib = libname.format("{{ config.cdylib_name() }}")
+ path = str(Path(__file__).parent / lib)
+ return ctypes.cdll.LoadLibrary(path)
+
+# A ctypes library to expose the extern-C FFI definitions.
+# This is an implementation detail which will be called internally by the public API.
+
+_UniFFILib = loadIndirect()
+{%- for func in ci.iter_ffi_function_definitions() %}
+_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 %}
+{%- endfor %}
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
new file mode 100644
index 0000000000..cc4cc35b4e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
@@ -0,0 +1,74 @@
+{%- let obj = ci.get_object_definition(name).unwrap() %}
+
+class {{ type_name }}(object):
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ def __init__(self, {% call py::arg_list_decl(cons) -%}):
+ {%- call py::setup_args_extra_indent(cons) %}
+ self._pointer = {% call py::to_ffi_call(cons) %}
+ {%- when None %}
+ {%- endmatch %}
+
+ def __del__(self):
+ # In case of partial initialization of instances.
+ pointer = getattr(self, "_pointer", None)
+ if pointer is not None:
+ rust_call(_UniFFILib.{{ obj.ffi_object_free().name() }}, pointer)
+
+ # Used by alternative constructors or any methods which return this type.
+ @classmethod
+ def _make_instance_(cls, pointer):
+ # Lightly yucky way to bypass the usual __init__ logic
+ # and just create a new instance with the required pointer.
+ inst = cls.__new__(cls)
+ inst._pointer = pointer
+ return inst
+
+ {% for cons in obj.alternate_constructors() -%}
+ @classmethod
+ def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
+ {%- 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)
+ {% endfor %}
+
+ {% for meth in obj.methods() -%}
+ {%- match meth.return_type() -%}
+
+ {%- when Some with (return_type) -%}
+ def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
+ {%- call py::setup_args_extra_indent(meth) %}
+ return {{ return_type|lift_fn }}(
+ {% call py::to_ffi_call_with_prefix("self._pointer", meth) %}
+ )
+
+ {%- when None -%}
+ def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
+ {%- call py::setup_args_extra_indent(meth) %}
+ {% call py::to_ffi_call_with_prefix("self._pointer", meth) %}
+ {% endmatch %}
+ {% endfor %}
+
+
+class {{ ffi_converter_name }}:
+ @classmethod
+ def read(cls, buf):
+ ptr = buf.readU64()
+ if ptr == 0:
+ raise InternalError("Raw pointer value was null")
+ return cls.lift(ptr)
+
+ @classmethod
+ def write(cls, value, buf):
+ if not isinstance(value, {{ type_name }}):
+ raise TypeError("Expected {{ type_name }} instance, {} found".format(value.__class__.__name__))
+ buf.writeU64(cls.lower(value))
+
+ @staticmethod
+ def lift(value):
+ return {{ type_name }}._make_instance_(value)
+
+ @staticmethod
+ def lower(value):
+ return value._pointer
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
new file mode 100644
index 0000000000..70f705362f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
@@ -0,0 +1,21 @@
+{%- let inner_ffi_converter = inner_type|ffi_converter_name %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, value, buf):
+ if value is None:
+ buf.writeU8(0)
+ return
+
+ buf.writeU8(1)
+ {{ inner_ffi_converter }}.write(value, buf)
+
+ @classmethod
+ def read(cls, buf):
+ flag = buf.readU8()
+ if flag == 0:
+ return None
+ elif flag == 1:
+ return {{ inner_ffi_converter }}.read(buf)
+ else:
+ raise InternalError("Unexpected flag byte for optional type")
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
new file mode 100644
index 0000000000..2009d59e8a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
@@ -0,0 +1,45 @@
+{%- let rec = ci.get_record_definition(name).unwrap() %}
+class {{ type_name }}:
+
+ def __init__(self, {% for field in rec.fields() %}
+ {{- field.name()|var_name }}
+ {%- if field.default_value().is_some() %} = DEFAULT{% endif %}
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}):
+ {%- for field in rec.fields() %}
+ {%- let field_name = field.name()|var_name %}
+ {%- match field.default_value() %}
+ {%- when None %}
+ self.{{ field_name }} = {{ field_name }}
+ {%- when Some with(literal) %}
+ if {{ field_name }} is DEFAULT:
+ self.{{ field_name }} = {{ literal|literal_py(field) }}
+ else:
+ self.{{ field_name }} = {{ field_name }}
+ {%- endmatch %}
+ {%- endfor %}
+
+ 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 %})
+
+ def __eq__(self, other):
+ {%- for field in rec.fields() %}
+ if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}:
+ return False
+ {%- endfor %}
+ return True
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ return {{ type_name }}(
+ {%- for field in rec.fields() %}
+ {{ field.name()|var_name }}={{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+
+ @staticmethod
+ def write(value, buf):
+ {%- for field in rec.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
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
new file mode 100644
index 0000000000..eafd2346bb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
@@ -0,0 +1,23 @@
+# Types conforming to `FfiConverterPrimitive` pass themselves directly over the FFI.
+class FfiConverterPrimitive:
+ @classmethod
+ def lift(cls, value):
+ return value
+
+ @classmethod
+ def lower(cls, value):
+ return value
+
+# Helper class for wrapper types that will always go through a RustBuffer.
+# Classes should inherit from this and implement the `read` and `write` static methods.
+class FfiConverterRustBuffer:
+ @classmethod
+ def lift(cls, rbuf):
+ with rbuf.consumeWithStream() as stream:
+ return cls.read(stream)
+
+ @classmethod
+ def lower(cls, value):
+ with RustBuffer.allocWithBuilder() as builder:
+ cls.write(value, builder)
+ return builder.finalize()
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
new file mode 100644
index 0000000000..95a3b1f706
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
@@ -0,0 +1,190 @@
+
+class RustBuffer(ctypes.Structure):
+ _fields_ = [
+ ("capacity", ctypes.c_int32),
+ ("len", ctypes.c_int32),
+ ("data", ctypes.POINTER(ctypes.c_char)),
+ ]
+
+ @staticmethod
+ def alloc(size):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_alloc().name() }}, size)
+
+ @staticmethod
+ def reserve(rbuf, additional):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_reserve().name() }}, rbuf, additional)
+
+ def free(self):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_free().name() }}, self)
+
+ def __str__(self):
+ return "RustBuffer(capacity={}, len={}, data={})".format(
+ self.capacity,
+ self.len,
+ self.data[0:self.len]
+ )
+
+ @contextlib.contextmanager
+ def allocWithBuilder():
+ """Context-manger to allocate a buffer using a RustBufferBuilder.
+
+ The allocated buffer will be automatically freed if an error occurs, ensuring that
+ we don't accidentally leak it.
+ """
+ builder = RustBufferBuilder()
+ try:
+ yield builder
+ except:
+ builder.discard()
+ raise
+
+ @contextlib.contextmanager
+ def consumeWithStream(self):
+ """Context-manager to consume a buffer using a RustBufferStream.
+
+ The RustBuffer will be freed once the context-manager exits, ensuring that we don't
+ leak it even if an error occurs.
+ """
+ try:
+ s = RustBufferStream(self)
+ yield s
+ if s.remaining() != 0:
+ raise RuntimeError("junk data left in buffer after consuming")
+ finally:
+ self.free()
+
+
+class ForeignBytes(ctypes.Structure):
+ _fields_ = [
+ ("len", ctypes.c_int32),
+ ("data", ctypes.POINTER(ctypes.c_char)),
+ ]
+
+ def __str__(self):
+ return "ForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len])
+
+
+class RustBufferStream(object):
+ """
+ Helper for structured reading of bytes from a RustBuffer
+ """
+
+ def __init__(self, rbuf):
+ self.rbuf = rbuf
+ self.offset = 0
+
+ def remaining(self):
+ return self.rbuf.len - self.offset
+
+ def _unpack_from(self, size, format):
+ if self.offset + size > self.rbuf.len:
+ raise InternalError("read past end of rust buffer")
+ value = struct.unpack(format, self.rbuf.data[self.offset:self.offset+size])[0]
+ self.offset += size
+ return value
+
+ def read(self, size):
+ if self.offset + size > self.rbuf.len:
+ raise InternalError("read past end of rust buffer")
+ data = self.rbuf.data[self.offset:self.offset+size]
+ self.offset += size
+ return data
+
+ def readI8(self):
+ return self._unpack_from(1, ">b")
+
+ def readU8(self):
+ return self._unpack_from(1, ">B")
+
+ def readI16(self):
+ return self._unpack_from(2, ">h")
+
+ def readU16(self):
+ return self._unpack_from(2, ">H")
+
+ def readI32(self):
+ return self._unpack_from(4, ">i")
+
+ def readU32(self):
+ return self._unpack_from(4, ">I")
+
+ def readI64(self):
+ return self._unpack_from(8, ">q")
+
+ def readU64(self):
+ return self._unpack_from(8, ">Q")
+
+ def readFloat(self):
+ v = self._unpack_from(4, ">f")
+ return v
+
+ def readDouble(self):
+ return self._unpack_from(8, ">d")
+
+
+class RustBufferBuilder(object):
+ """
+ Helper for structured writing of bytes into a RustBuffer.
+ """
+
+ def __init__(self):
+ self.rbuf = RustBuffer.alloc(16)
+ self.rbuf.len = 0
+
+ def finalize(self):
+ rbuf = self.rbuf
+ self.rbuf = None
+ return rbuf
+
+ def discard(self):
+ if self.rbuf is not None:
+ rbuf = self.finalize()
+ rbuf.free()
+
+ @contextlib.contextmanager
+ def _reserve(self, numBytes):
+ if self.rbuf.len + numBytes > self.rbuf.capacity:
+ self.rbuf = RustBuffer.reserve(self.rbuf, numBytes)
+ yield None
+ self.rbuf.len += numBytes
+
+ def _pack_into(self, size, format, value):
+ with self._reserve(size):
+ # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out.
+ for i, byte in enumerate(struct.pack(format, value)):
+ self.rbuf.data[self.rbuf.len + i] = byte
+
+ def write(self, value):
+ with self._reserve(len(value)):
+ for i, byte in enumerate(value):
+ self.rbuf.data[self.rbuf.len + i] = byte
+
+ def writeI8(self, v):
+ self._pack_into(1, ">b", v)
+
+ def writeU8(self, v):
+ self._pack_into(1, ">B", v)
+
+ def writeI16(self, v):
+ self._pack_into(2, ">h", v)
+
+ def writeU16(self, v):
+ self._pack_into(2, ">H", v)
+
+ def writeI32(self, v):
+ self._pack_into(4, ">i", v)
+
+ def writeU32(self, v):
+ self._pack_into(4, ">I", v)
+
+ def writeI64(self, v):
+ self._pack_into(8, ">q", v)
+
+ def writeU64(self, v):
+ self._pack_into(8, ">Q", v)
+
+ def writeFloat(self, v):
+ self._pack_into(4, ">f", v)
+
+ def writeDouble(self, v):
+ self._pack_into(8, ">d", v)
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
new file mode 100644
index 0000000000..6de7bf2d4d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
@@ -0,0 +1,19 @@
+{%- let inner_ffi_converter = inner_type|ffi_converter_name %}
+
+class {{ ffi_converter_name}}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, value, buf):
+ items = len(value)
+ buf.writeI32(items)
+ for item in value:
+ {{ inner_ffi_converter }}.write(item, buf)
+
+ @classmethod
+ def read(cls, buf):
+ count = buf.readI32()
+ if count < 0:
+ raise InternalError("Unexpected negative sequence length")
+
+ return [
+ {{ inner_ffi_converter }}.read(buf) for i in range(count)
+ ]
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
new file mode 100644
index 0000000000..f205f7c746
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
@@ -0,0 +1,25 @@
+class FfiConverterString:
+ @staticmethod
+ def read(buf):
+ size = buf.readI32()
+ if size < 0:
+ raise InternalError("Unexpected negative string length")
+ utf8Bytes = buf.read(size)
+ return utf8Bytes.decode("utf-8")
+
+ @staticmethod
+ def write(value, buf):
+ utf8Bytes = value.encode("utf-8")
+ buf.writeI32(len(utf8Bytes))
+ buf.write(utf8Bytes)
+
+ @staticmethod
+ def lift(buf):
+ with buf.consumeWithStream() as stream:
+ return stream.read(stream.remaining()).decode("utf-8")
+
+ @staticmethod
+ def lower(value):
+ with RustBuffer.allocWithBuilder() 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
new file mode 100644
index 0000000000..ac87697551
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
@@ -0,0 +1,30 @@
+# The Timestamp type.
+# There is a loss of precision when converting from Rust timestamps,
+# which are accurate to the nanosecond,
+# to Python datetimes, which have a variable precision due to the use of float as representation.
+class FfiConverterTimestamp(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ seconds = buf.readI64()
+ microseconds = buf.readU32() / 1000
+ # Use fromtimestamp(0) then add the seconds using a timedelta. This
+ # ensures that we get OverflowError rather than ValueError when
+ # seconds is too large.
+ if seconds >= 0:
+ return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) + datetime.timedelta(seconds=seconds, microseconds=microseconds)
+ else:
+ return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds)
+
+ @staticmethod
+ def write(value, buf):
+ if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc):
+ sign = 1
+ delta = value - datetime.datetime.fromtimestamp(0, datetime.timezone.utc)
+ else:
+ sign = -1
+ delta = datetime.datetime.fromtimestamp(0, datetime.timezone.utc) - value
+
+ seconds = delta.seconds + delta.days * 24 * 3600
+ nanoseconds = delta.microseconds * 1000
+ buf.writeI64(sign * seconds)
+ buf.writeU32(nanoseconds)
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
new file mode 100644
index 0000000000..2d0e09f22d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
@@ -0,0 +1,13 @@
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
+ {%- 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) -%}):
+ {%- 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
new file mode 100644
index 0000000000..ce65472a64
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
@@ -0,0 +1,93 @@
+{%- import "macros.py" as py %}
+
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+
+{#
+ # Map `Type` instances to an include statement for that type.
+ #
+ # There is a companion match in `PythonCodeOracle::create_code_type()` which performs a similar function for the
+ # Rust code.
+ #
+ # - When adding additional types here, make sure to also add a match arm to that function.
+ # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ #}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "BooleanHelper.py" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8Helper.py" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16Helper.py" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32Helper.py" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64Helper.py" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8Helper.py" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16Helper.py" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32Helper.py" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64Helper.py" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32Helper.py" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64Helper.py" %}
+
+{%- when Type::String %}
+{%- include "StringHelper.py" %}
+
+{%- when Type::Enum(name) %}
+{%- include "EnumTemplate.py" %}
+
+{%- when Type::Error(name) %}
+{%- include "ErrorTemplate.py" %}
+
+{%- when Type::Record(name) %}
+{%- include "RecordTemplate.py" %}
+
+{%- when Type::Object(name) %}
+{%- include "ObjectTemplate.py" %}
+
+{%- when Type::Timestamp %}
+{%- include "TimestampHelper.py" %}
+
+{%- when Type::Duration %}
+{%- include "DurationHelper.py" %}
+
+{%- when Type::Optional(inner_type) %}
+{%- include "OptionalTemplate.py" %}
+
+{%- when Type::Sequence(inner_type) %}
+{%- include "SequenceTemplate.py" %}
+
+{%- when Type::Map(key_type, value_type) %}
+{%- include "MapTemplate.py" %}
+
+{%- when Type::CallbackInterface(id) %}
+{%- include "CallbackInterfaceTemplate.py" %}
+
+{%- when Type::Custom { name, builtin } %}
+{%- include "CustomType.py" %}
+
+{%- when Type::External { name, crate_name } %}
+{%- include "ExternalTemplate.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
new file mode 100644
index 0000000000..f1cd114c0c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt16(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU16()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU16(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
new file mode 100644
index 0000000000..290cd5fe82
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt32(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU32()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU32(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
new file mode 100644
index 0000000000..f57dec1d92
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt64(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU64()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU64(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
new file mode 100644
index 0000000000..3a1582351b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt8(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU8()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU8(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
new file mode 100644
index 0000000000..d11fcae921
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
@@ -0,0 +1,101 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call`
+#}
+
+{%- 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_ffi_call(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 -%}
+rust_call(
+ {%- endmatch -%}
+ _UniFFILib.{{ func.ffi_func().name() }},
+ {{- prefix }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg|lower_fn }}({{ arg.name()|var_name }})
+ {%- if !loop.last %},{% endif %}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in Python declarations of methods, functions and constructors.
+// Note the var_name and type_name filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = DEFAULT
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %},{% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{#-
+// 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.type_().borrow()|ffi_type_name }},
+ {%- endfor %}
+ ctypes.POINTER(RustCallStatus),
+{% endmacro -%}
+
+{#
+ # Setup function arguments by initializing default values and passing other
+ # values through coerce.
+ #}
+{%- macro setup_args(func) %}
+ {%- for arg in func.arguments() %}
+ {%- match arg.default_value() %}
+ {%- when None %}
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- when Some with(literal) %}
+ if {{ arg.name()|var_name }} is DEFAULT:
+ {{ arg.name()|var_name }} = {{ literal|literal_py(arg.type_().borrow()) }}
+ else:
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- endmatch %}
+ {% endfor -%}
+{%- endmacro -%}
+
+{#
+ # Exactly the same thing as `setup_args()` but with an extra 4 spaces of
+ # indent so that it works with object methods.
+ #}
+{%- macro setup_args_extra_indent(func) %}
+ {%- for arg in func.arguments() %}
+ {%- match arg.default_value() %}
+ {%- when None %}
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- when Some with(literal) %}
+ if {{ arg.name()|var_name }} is DEFAULT:
+ {{ arg.name()|var_name }} = {{ literal|literal_py(arg.type_().borrow()) }}
+ else:
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- endmatch %}
+ {% endfor -%}
+{%- 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
new file mode 100644
index 0000000000..d30d3c9d12
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
@@ -0,0 +1,71 @@
+# This file was autogenerated by some hot garbage in the `uniffi` crate.
+# Trust me, you don't want to mess with it!
+
+# Tell mypy (a type checker) to ignore all errors from this file.
+# See https://mypy.readthedocs.io/en/stable/config_file.html?highlight=ignore-errors#confval-ignore_errors
+# mypy: ignore-errors
+
+# Common helper code.
+#
+# Ideally this would live in a separate .py file where it can be unittested etc
+# in isolation, and perhaps even published as a re-useable package.
+#
+# However, it's important that the details of how this helper code works (e.g. the
+# way that different builtin types are passed across the FFI) exactly match what's
+# expected by the rust code on the other side of the interface. In practice right
+# now that means coming from the exact some version of `uniffi` that was used to
+# compile the rust component. The easiest way to ensure this is to bundle the Python
+# helpers directly inline like we're doing here.
+
+import os
+import sys
+import ctypes
+import enum
+import struct
+import contextlib
+import datetime
+{%- for req in self.imports() %}
+{{ req.render() }}
+{%- endfor %}
+
+# Used for default argument values
+DEFAULT = object()
+
+{% include "RustBufferTemplate.py" %}
+{% include "Helpers.py" %}
+{% include "RustBufferHelper.py" %}
+
+# Contains loading, initialization code,
+# and the FFI Function declarations in a com.sun.jna.Library.
+{% include "NamespaceLibraryTemplate.py" %}
+
+# Public interface members begin here.
+{{ type_helper_code }}
+
+{%- for func in ci.function_definitions() %}
+{%- include "TopLevelFunctionTemplate.py" %}
+{%- endfor %}
+
+__all__ = [
+ "InternalError",
+ {%- for e in ci.enum_definitions() %}
+ "{{ e|type_name }}",
+ {%- endfor %}
+ {%- for record in ci.record_definitions() %}
+ "{{ record|type_name }}",
+ {%- endfor %}
+ {%- for func in ci.function_definitions() %}
+ "{{ func.name()|fn_name }}",
+ {%- endfor %}
+ {%- for obj in ci.object_definitions() %}
+ "{{ obj|type_name }}",
+ {%- endfor %}
+ {%- for e in ci.error_definitions() %}
+ "{{ e|type_name }}",
+ {%- endfor %}
+ {%- for c in ci.callback_interface_definitions() %}
+ "{{ c.name()|class_name }}",
+ {%- endfor %}
+]
+
+{% 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
new file mode 100644
index 0000000000..d050fc0389
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
@@ -0,0 +1,62 @@
+/* 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 anyhow::{Context, Result};
+use camino::Utf8Path;
+use std::env;
+use std::ffi::OsString;
+use std::process::{Command, Stdio};
+use uniffi_testing::UniFFITestHelper;
+
+/// Run Python tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_cdylibs_to_out_dir")?;
+ generate_sources(&test_helper.cdylib_path()?, &out_dir, &test_helper)
+ .context("generate_sources")?;
+
+ let pythonpath = env::var_os("PYTHONPATH").unwrap_or_else(|| OsString::from(""));
+ let pythonpath = env::join_paths(
+ env::split_paths(&pythonpath).chain(vec![out_dir.to_path_buf().into_std_path_buf()]),
+ )?;
+
+ let status = Command::new("python3")
+ .current_dir(out_dir)
+ .env("PYTHONPATH", pythonpath)
+ .arg(script_path)
+ .stderr(Stdio::inherit())
+ .stdout(Stdio::inherit())
+ .spawn()
+ .context("Failed to spawn `python3` when running script")?
+ .wait()
+ .context("Failed to wait for `python3` when running script")?;
+ if !status.success() {
+ anyhow::bail!("running `python3` failed");
+ }
+ Ok(())
+}
+
+fn generate_sources(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+) -> Result<()> {
+ for source in test_helper.get_compile_sources()? {
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["python"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+ Ok(())
+}
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
new file mode 100644
index 0000000000..9d0fc55bdc
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
@@ -0,0 +1,273 @@
+/* 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 anyhow::Result;
+use askama::Template;
+use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
+use serde::{Deserialize, Serialize};
+use std::borrow::Borrow;
+
+use crate::interface::*;
+use crate::MergeWith;
+
+const RESERVED_WORDS: &[&str] = &[
+ "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
+ "elsif", "END", "end", "ensure", "false", "for", "if", "module", "next", "nil", "not", "or",
+ "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless",
+ "until", "when", "while", "yield", "__FILE__", "__LINE__",
+];
+
+fn is_reserved_word(word: &str) -> bool {
+ RESERVED_WORDS.contains(&word)
+}
+
+// Some config options for it the caller wants to customize the generated ruby.
+// Note that this can only be used to control details of the ruby *that do not affect the underlying component*,
+// since the details of the underlying component are entirely determined by the `ComponentInterface`.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ cdylib_name: Option<String>,
+ cdylib_path: Option<String>,
+}
+
+impl Config {
+ pub fn cdylib_name(&self) -> String {
+ self.cdylib_name
+ .clone()
+ .unwrap_or_else(|| "uniffi".to_string())
+ }
+
+ pub fn custom_cdylib_path(&self) -> bool {
+ self.cdylib_path.is_some()
+ }
+
+ pub fn cdylib_path(&self) -> String {
+ self.cdylib_path.clone().unwrap_or_default()
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ cdylib_path: None,
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ cdylib_path: self.cdylib_path.merge_with(&other.cdylib_path),
+ }
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "rb", escape = "none", path = "wrapper.rb")]
+pub struct RubyWrapper<'a> {
+ config: Config,
+ ci: &'a ComponentInterface,
+}
+impl<'a> RubyWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ Self { config, ci }
+ }
+}
+
+mod filters {
+ use super::*;
+
+ pub fn type_ffi(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ FfiType::Int8 => ":int8".to_string(),
+ FfiType::UInt8 => ":uint8".to_string(),
+ FfiType::Int16 => ":int16".to_string(),
+ FfiType::UInt16 => ":uint16".to_string(),
+ FfiType::Int32 => ":int32".to_string(),
+ FfiType::UInt32 => ":uint32".to_string(),
+ FfiType::Int64 => ":int64".to_string(),
+ FfiType::UInt64 => ":uint64".to_string(),
+ FfiType::Float32 => ":float".to_string(),
+ FfiType::Float64 => ":double".to_string(),
+ FfiType::RustArcPtr(_) => ":pointer".to_string(),
+ FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(),
+ FfiType::ForeignBytes => "ForeignBytes".to_string(),
+ FfiType::ForeignCallback => unimplemented!("Callback interfaces are not implemented"),
+ })
+ }
+
+ pub fn literal_rb(literal: &Literal) -> Result<String, askama::Error> {
+ Ok(match literal {
+ Literal::Boolean(v) => {
+ if *v {
+ "true".into()
+ } else {
+ "false".into()
+ }
+ }
+ // use the double-quote form to match with the other languages, and quote escapes.
+ Literal::String(s) => format!("\"{s}\""),
+ Literal::Null => "nil".into(),
+ Literal::EmptySequence => "[]".into(),
+ Literal::EmptyMap => "{}".into(),
+ Literal::Enum(v, type_) => match type_ {
+ Type::Enum(name) => format!("{}::{}", class_name_rb(name)?, enum_name_rb(v)?),
+ _ => panic!("Unexpected type in enum literal: {type_:?}"),
+ },
+ // https://docs.ruby-lang.org/en/2.0.0/syntax/literals_rdoc.html
+ Literal::Int(i, radix, _) => match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::UInt(i, radix, _) => match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::Float(string, _type_) => string.clone(),
+ })
+ }
+
+ pub fn class_name_rb(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.to_string().to_upper_camel_case())
+ }
+
+ pub fn fn_name_rb(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.to_string().to_snake_case())
+ }
+
+ pub fn var_name_rb(nm: &str) -> Result<String, askama::Error> {
+ let nm = nm.to_string();
+ let prefix = if is_reserved_word(&nm) { "_" } else { "" };
+
+ Ok(format!("{prefix}{}", nm.to_snake_case()))
+ }
+
+ pub fn enum_name_rb(nm: &str) -> Result<String, askama::Error> {
+ Ok(nm.to_string().to_shouty_snake_case())
+ }
+
+ pub fn coerce_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Int8
+ | Type::UInt8
+ | Type::Int16
+ | Type::UInt16
+ | Type::Int32
+ | Type::UInt32
+ | Type::Int64
+ | Type::UInt64 => format!("{nm}.to_i"), // TODO: check max/min value
+ Type::Float32 | Type::Float64 => format!("{nm}.to_f"),
+ Type::Boolean => format!("{nm} ? true : false"),
+ Type::Object(_) | Type::Enum(_) | Type::Error(_) | Type::Record(_) => nm.to_string(),
+ Type::String => format!("{nm}.to_s"),
+ Type::Timestamp | Type::Duration => nm.to_string(),
+ Type::CallbackInterface(_) => panic!("No support for coercing callback interfaces yet"),
+ Type::Optional(t) => format!("({nm} ? {} : nil)", coerce_rb(nm, t)?),
+ Type::Sequence(t) => {
+ let coerce_code = coerce_rb("v", t)?;
+ if coerce_code == "v" {
+ nm.to_string()
+ } else {
+ format!("{nm}.map {{ |v| {coerce_code} }}")
+ }
+ }
+ Type::Map(_k, t) => {
+ let k_coerce_code = coerce_rb("k", &Type::String)?;
+ let v_coerce_code = coerce_rb("v", t)?;
+
+ if k_coerce_code == "k" && v_coerce_code == "v" {
+ nm.to_string()
+ } else {
+ format!(
+ "{}.each.with_object({{}}) {{ |(k, v), res| res[{}] = {} }}",
+ nm, k_coerce_code, v_coerce_code,
+ )
+ }
+ }
+ Type::External { .. } => panic!("No support for external types, yet"),
+ Type::Custom { .. } => panic!("No support for custom types, yet"),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling coerce_rb")
+ }
+ })
+ }
+
+ pub fn lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Int8
+ | Type::UInt8
+ | Type::Int16
+ | Type::UInt16
+ | Type::Int32
+ | Type::UInt32
+ | Type::Int64
+ | Type::UInt64
+ | Type::Float32
+ | Type::Float64 => nm.to_string(),
+ Type::Boolean => format!("({nm} ? 1 : 0)"),
+ Type::String => format!("RustBuffer.allocFromString({nm})"),
+ Type::Object(name) => format!("({}._uniffi_lower {nm})", class_name_rb(name)?),
+ Type::CallbackInterface(_) => panic!("No support for lowering callback interfaces yet"),
+ Type::Error(_) => panic!("No support for lowering errors, yet"),
+ Type::Enum(_)
+ | Type::Record(_)
+ | Type::Optional(_)
+ | Type::Sequence(_)
+ | Type::Timestamp
+ | Type::Duration
+ | Type::Map(_, _) => format!(
+ "RustBuffer.alloc_from_{}({})",
+ class_name_rb(&type_.canonical_name())?,
+ nm
+ ),
+ Type::External { .. } => panic!("No support for lowering external types, yet"),
+ Type::Custom { .. } => panic!("No support for lowering custom types, yet"),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling lower_rb")
+ }
+ })
+ }
+
+ pub fn lift_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Int8
+ | Type::UInt8
+ | Type::Int16
+ | Type::UInt16
+ | Type::Int32
+ | Type::UInt32
+ | Type::Int64
+ | Type::UInt64 => format!("{nm}.to_i"),
+ Type::Float32 | Type::Float64 => format!("{nm}.to_f"),
+ Type::Boolean => format!("1 == {nm}"),
+ Type::String => format!("{nm}.consumeIntoString"),
+ Type::Object(name) => format!("{}._uniffi_allocate({nm})", class_name_rb(name)?),
+ Type::CallbackInterface(_) => panic!("No support for lifting callback interfaces, yet"),
+ Type::Error(_) => panic!("No support for lowering errors, yet"),
+ Type::Enum(_)
+ | Type::Record(_)
+ | Type::Optional(_)
+ | Type::Sequence(_)
+ | Type::Timestamp
+ | Type::Duration
+ | Type::Map(_, _) => format!(
+ "{}.consumeInto{}",
+ nm,
+ class_name_rb(&type_.canonical_name())?
+ ),
+ Type::External { .. } => panic!("No support for lifting external types, yet"),
+ Type::Custom { .. } => panic!("No support for lifting custom types, yet"),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling lift_rb")
+ }
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs
new file mode 100644
index 0000000000..9ae5d1816f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs
@@ -0,0 +1,47 @@
+use super::{is_reserved_word, Config};
+
+#[test]
+fn when_reserved_word() {
+ assert!(is_reserved_word("end"));
+}
+
+#[test]
+fn when_not_reserved_word() {
+ assert!(!is_reserved_word("ruby"));
+}
+
+#[test]
+fn cdylib_name() {
+ let config = Config {
+ cdylib_name: None,
+ cdylib_path: None,
+ };
+
+ assert_eq!("uniffi", config.cdylib_name());
+
+ let config = Config {
+ cdylib_name: Some("todolist".to_string()),
+ cdylib_path: None,
+ };
+
+ assert_eq!("todolist", config.cdylib_name());
+}
+
+#[test]
+fn cdylib_path() {
+ let config = Config {
+ cdylib_name: None,
+ cdylib_path: None,
+ };
+
+ assert_eq!("", config.cdylib_path());
+ assert!(!config.custom_cdylib_path());
+
+ let config = Config {
+ cdylib_name: None,
+ cdylib_path: Some("/foo/bar".to_string()),
+ };
+
+ assert_eq!("/foo/bar", config.cdylib_path());
+ assert!(config.custom_cdylib_path());
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs
new file mode 100644
index 0000000000..e0d789f42f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs
@@ -0,0 +1,50 @@
+/* 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 std::{io::Write, process::Command};
+
+use anyhow::{Context, Result};
+use camino::Utf8Path;
+use fs_err::File;
+
+pub mod gen_ruby;
+mod test;
+pub use gen_ruby::{Config, RubyWrapper};
+pub use test::run_test;
+
+use super::super::interface::ComponentInterface;
+
+// Generate ruby bindings for the given ComponentInterface, in the given output directory.
+
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let rb_file = out_dir.join(format!("{}.rb", ci.namespace()));
+ let mut f = File::create(&rb_file)?;
+ write!(f, "{}", generate_ruby_bindings(config, ci)?)?;
+
+ if try_format_code {
+ if let Err(e) = Command::new("rubocop").arg("-A").arg(&rb_file).output() {
+ println!(
+ "Warning: Unable to auto-format {} using rubocop: {:?}",
+ rb_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+
+ Ok(())
+}
+
+// Generate ruby bindings for the given ComponentInterface, as a string.
+
+pub fn generate_ruby_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
+ use askama::Template;
+ RubyWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render ruby bindings")
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb
new file mode 100644
index 0000000000..23b701f6a7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb
@@ -0,0 +1,59 @@
+{% if e.is_flat() %}
+
+class {{ e.name()|class_name_rb }}
+ {% for variant in e.variants() -%}
+ {{ variant.name()|enum_name_rb }} = {{ loop.index }}
+ {% endfor %}
+end
+
+{% else %}
+
+class {{ e.name()|class_name_rb }}
+ def initialize
+ raise RuntimeError, '{{ e.name()|class_name_rb }} cannot be instantiated directly'
+ end
+
+ # Each enum variant is a nested class of the enum itself.
+ {% for variant in e.variants() -%}
+ class {{ variant.name()|enum_name_rb }}
+ {% if variant.has_fields() %}
+ attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %}
+ {% endif %}
+ def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+ {% if variant.has_fields() %}
+ {%- for field in variant.fields() %}
+ @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
+ {%- endfor %}
+ {% else %}
+ {% endif %}
+ end
+
+ def to_s
+ "{{ e.name()|class_name_rb }}::{{ variant.name()|enum_name_rb }}({% for field in variant.fields() %}{{ field.name() }}=#{@{{ field.name() }}}{% if loop.last %}{% else %}, {% endif %}{% endfor %})"
+ end
+
+ def ==(other)
+ if !other.{{ variant.name()|var_name_rb }}?
+ return false
+ end
+ {%- for field in variant.fields() %}
+ if @{{ field.name()|var_name_rb }} != other.{{ field.name()|var_name_rb }}
+ return false
+ end
+ {%- endfor %}
+
+ true
+ end
+
+ # For each variant, we have an `NAME?` method for easily checking
+ # whether an instance is that variant.
+ {% for variant in e.variants() %}
+ def {{ variant.name()|var_name_rb }}?
+ instance_of? {{ e.name()|class_name_rb }}::{{ variant.name()|enum_name_rb }}
+ end
+ {% endfor %}
+ end
+ {% endfor %}
+end
+
+{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb
new file mode 100644
index 0000000000..3a64e9ffeb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb
@@ -0,0 +1,117 @@
+class RustCallStatus < FFI::Struct
+ layout :code, :int8,
+ :error_buf, RustBuffer
+
+ def code
+ self[:code]
+ end
+
+ def error_buf
+ self[:error_buf]
+ end
+
+ def to_s
+ "RustCallStatus(code=#{self[:code]})"
+ end
+end
+
+# These match the values from the uniffi::rustcalls module
+CALL_SUCCESS = 0
+CALL_ERROR = 1
+CALL_PANIC = 2
+{%- for e in ci.error_definitions() %}
+{% if e.is_flat() %}
+class {{ e.name()|class_name_rb }}
+ {%- for variant in e.variants() %}
+ {{ variant.name()|class_name_rb }} = Class.new StandardError
+ {%- endfor %}
+{% else %}
+module {{ e.name()|class_name_rb }}
+ {%- for variant in e.variants() %}
+ class {{ variant.name()|class_name_rb }} < StandardError
+ def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %})
+ {%- for field in variant.fields() %}
+ @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
+ {%- endfor %}
+ super()
+ end
+ {%- if variant.has_fields() %}
+
+ attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %}
+ {% endif %}
+
+ def to_s
+ "#{self.class.name}({% for field in variant.fields() %}{{ field.name()|var_name_rb }}=#{@{{ field.name()|var_name_rb }}.inspect}{% if !loop.last %}, {% endif %}{% endfor %})"
+ end
+ end
+ {%- endfor %}
+{% endif %}
+end
+{%- endfor %}
+
+# Map error modules to the RustBuffer method name that reads them
+ERROR_MODULE_TO_READER_METHOD = {
+{%- for e in ci.error_definitions() %}
+{%- let typ=ci.get_type(e.name()).unwrap() %}
+{%- let canonical_type_name = typ.canonical_name().borrow()|class_name_rb %}
+ {{ e.name()|class_name_rb }} => :read{{ canonical_type_name }},
+{%- endfor %}
+}
+
+private_constant :ERROR_MODULE_TO_READER_METHOD, :CALL_SUCCESS, :CALL_ERROR, :CALL_PANIC,
+ :RustCallStatus
+
+def self.consume_buffer_into_error(error_module, rust_buffer)
+ rust_buffer.consumeWithStream do |stream|
+ reader_method = ERROR_MODULE_TO_READER_METHOD[error_module]
+ return stream.send(reader_method)
+ end
+end
+
+class InternalError < StandardError
+end
+
+def self.rust_call(fn_name, *args)
+ # Call a rust function
+ rust_call_with_error(nil, fn_name, *args)
+end
+
+def self.rust_call_with_error(error_module, fn_name, *args)
+ # Call a rust function and handle errors
+ #
+ # Use this when the rust function returns a Result<>. error_module must be the error_module that corresponds to that Result.
+
+
+ # Note: RustCallStatus.new zeroes out the struct, which is exactly what we
+ # want to pass to Rust (code=0, error_buf=RustBuffer(len=0, capacity=0,
+ # data=NULL))
+ status = RustCallStatus.new
+ args << status
+
+ result = UniFFILib.public_send(fn_name, *args)
+
+ case status.code
+ when CALL_SUCCESS
+ result
+ when CALL_ERROR
+ if error_module.nil?
+ status.error_buf.free
+ raise InternalError, "CALL_ERROR with no error_module set"
+ else
+ raise consume_buffer_into_error(error_module, status.error_buf)
+ end
+ when CALL_PANIC
+ # 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.
+ if status.error_buf.len > 0
+ raise InternalError, status.error_buf.consumeIntoString()
+ else
+ raise InternalError, "Rust panic"
+ end
+ else
+ raise InternalError, "Unknown call status: #{status.code}"
+ end
+end
+
+private_class_method :consume_buffer_into_error
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb
new file mode 100644
index 0000000000..858b42bf91
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb
@@ -0,0 +1,17 @@
+# This is how we find and load the dynamic library provided by the component.
+# For now we just look it up by name.
+module UniFFILib
+ extend FFI::Library
+
+ {% if config.custom_cdylib_path() %}
+ ffi_lib {{ config.cdylib_path() }}
+ {% else %}
+ ffi_lib '{{ config.cdylib_name() }}'
+ {% endif %}
+
+ {% for func in ci.iter_ffi_function_definitions() -%}
+ attach_function :{{ func.name() }},
+ {%- call rb::arg_list_ffi_decl(func) %},
+ {% match func.return_type() %}{% when Some with (type_) %}{{ type_|type_ffi }}{% when None %}:void{% endmatch %}
+ {% endfor %}
+end
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
new file mode 100644
index 0000000000..677c5c729b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
@@ -0,0 +1,73 @@
+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)
+ pointer.autorelease = false
+ inst = allocate
+ inst.instance_variable_set :@pointer, pointer
+ 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)
+ Proc.new do |_id|
+ {{ ci.namespace()|class_name_rb }}.rust_call(
+ :{{ obj.ffi_object_free().name() }},
+ pointer
+ )
+ end
+ end
+
+ # 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)
+ 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
+
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ def initialize({% call rb::arg_list_decl(cons) -%})
+ {%- call rb::coerce_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))
+ 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 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) %})
+ end
+ {% endfor %}
+
+ {% for meth in obj.methods() -%}
+ {%- match meth.return_type() -%}
+
+ {%- 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) %}
+ 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) %}
+ end
+ {% endmatch %}
+ {% endfor %}
+end
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
new file mode 100644
index 0000000000..c940b31060
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
@@ -0,0 +1,20 @@
+# Record type {{ rec.name() }}
+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 %})
+ {%- for field in rec.fields() %}
+ @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
+ {%- endfor %}
+ end
+
+ def ==(other)
+ {%- for field in rec.fields() %}
+ if @{{ field.name()|var_name_rb }} != other.{{ field.name()|var_name_rb }}
+ return false
+ end
+ {%- endfor %}
+
+ true
+ end
+end
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
new file mode 100644
index 0000000000..e4b3910b6c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
@@ -0,0 +1,246 @@
+
+# Helper for structured writing of values into a RustBuffer.
+class RustBufferBuilder
+ def initialize
+ @rust_buf = RustBuffer.alloc 16
+ @rust_buf.len = 0
+ end
+
+ def finalize
+ rbuf = @rust_buf
+
+ @rust_buf = nil
+
+ rbuf
+ end
+
+ def discard
+ return if @rust_buf.nil?
+
+ rbuf = finalize
+ rbuf.free
+ end
+
+ def write(value)
+ reserve(value.bytes.size) do
+ @rust_buf.data.put_array_of_char @rust_buf.len, value.bytes
+ end
+ end
+
+ {% for typ in ci.iter_types() -%}
+ {%- let canonical_type_name = typ.canonical_name().borrow()|class_name_rb -%}
+ {%- match typ -%}
+
+ {% when Type::Int8 -%}
+
+ def write_I8(v)
+ pack_into(1, 'c', v)
+ end
+
+ {% when Type::UInt8 -%}
+
+ def write_U8(v)
+ pack_into(1, 'c', v)
+ end
+
+ {% when Type::Int16 -%}
+
+ def write_I16(v)
+ pack_into(2, 's>', v)
+ end
+
+ {% when Type::UInt16 -%}
+
+ def write_U16(v)
+ pack_into(2, 'S>', v)
+ end
+
+ {% when Type::Int32 -%}
+
+ def write_I32(v)
+ pack_into(4, 'l>', v)
+ end
+
+ {% when Type::UInt32 -%}
+
+ def write_U32(v)
+ pack_into(4, 'L>', v)
+ end
+
+ {% when Type::Int64 -%}
+
+ def write_I64(v)
+ pack_into(8, 'q>', v)
+ end
+
+ {% when Type::UInt64 -%}
+
+ def write_U64(v)
+ pack_into(8, 'Q>', v)
+ end
+
+ {% when Type::Float32 -%}
+
+ def write_F32(v)
+ pack_into(4, 'g', v)
+ end
+
+ {% when Type::Float64 -%}
+
+ def write_F64(v)
+ pack_into(8, 'G', v)
+ end
+
+ {% when Type::Boolean -%}
+
+ def write_Bool(v)
+ pack_into(1, 'c', v ? 1 : 0)
+ end
+
+ {% when Type::String -%}
+
+ def write_String(v)
+ v = v.to_s
+ pack_into 4, 'l>', v.bytes.size
+ write v
+ end
+
+ {% when Type::Timestamp -%}
+ # The Timestamp type.
+ ONE_SECOND_IN_NANOSECONDS = 10**9
+
+ def write_{{ canonical_type_name }}(v)
+ seconds = v.tv_sec
+ nanoseconds = v.tv_nsec
+
+ # UniFFi conventions assume that nanoseconds part has to represent nanoseconds portion of
+ # duration between epoch and the timestamp moment. Ruby `Time#tv_nsec` returns the number of
+ # nanoseconds for the subsecond part, which is sort of opposite to "duration" meaning.
+ # Hence we need to convert value returned by `Time#tv_nsec` back and forth with the following
+ # logic:
+ if seconds < 0 && nanoseconds != 0
+ # In order to get duration nsec we shift by 1 second:
+ nanoseconds = ONE_SECOND_IN_NANOSECONDS - nanoseconds
+
+ # Then we compensate 1 second shift:
+ seconds += 1
+ end
+
+ pack_into 8, 'q>', seconds
+ pack_into 4, 'L>', nanoseconds
+ end
+
+ {% when Type::Duration -%}
+ # The Duration type.
+
+ def write_{{ canonical_type_name }}(v)
+ seconds = v.tv_sec
+ nanoseconds = v.tv_nsec
+
+ raise ArgumentError, 'Invalid duration, must be non-negative' if seconds < 0
+
+ pack_into 8, 'Q>', seconds
+ pack_into 4, 'L>', nanoseconds
+ end
+
+ {% when Type::Object with (object_name) -%}
+ # The Object type {{ object_name }}.
+
+ def write_{{ canonical_type_name }}(obj)
+ pointer = {{ object_name|class_name_rb}}._uniffi_lower obj
+ pack_into(8, 'Q>', pointer.address)
+ end
+
+ {% when Type::Enum with (enum_name) -%}
+ {%- let e = ci.get_enum_definition(enum_name).unwrap() -%}
+ # The Enum type {{ enum_name }}.
+
+ def write_{{ canonical_type_name }}(v)
+ {%- if e.is_flat() %}
+ pack_into(4, 'l>', v)
+ {%- else -%}
+ {%- for variant in e.variants() %}
+ if v.{{ variant.name()|var_name_rb }}?
+ pack_into(4, 'l>', {{ loop.index }})
+ {%- for field in variant.fields() %}
+ self.write_{{ field.type_().canonical_name().borrow()|class_name_rb }}(v.{{ field.name() }})
+ {%- endfor %}
+ end
+ {%- endfor %}
+ {%- endif %}
+ end
+
+ {% when Type::Record with (record_name) -%}
+ {%- let rec = ci.get_record_definition(record_name).unwrap() -%}
+ # The Record type {{ record_name }}.
+
+ def write_{{ canonical_type_name }}(v)
+ {%- for field in rec.fields() %}
+ self.write_{{ field.type_().canonical_name().borrow()|class_name_rb }}(v.{{ field.name()|var_name_rb }})
+ {%- endfor %}
+ end
+
+ {% when Type::Optional with (inner_type) -%}
+ # The Optional<T> type for {{ inner_type.canonical_name() }}.
+
+ def write_{{ canonical_type_name }}(v)
+ if v.nil?
+ pack_into(1, 'c', 0)
+ else
+ pack_into(1, 'c', 1)
+ self.write_{{ inner_type.canonical_name().borrow()|class_name_rb }}(v)
+ end
+ end
+
+ {% when Type::Sequence with (inner_type) -%}
+ # The Sequence<T> type for {{ inner_type.canonical_name() }}.
+
+ def write_{{ canonical_type_name }}(items)
+ pack_into(4, 'l>', items.size)
+
+ items.each do |item|
+ self.write_{{ inner_type.canonical_name().borrow()|class_name_rb }}(item)
+ end
+ end
+
+ {% when Type::Map with (k, inner_type) -%}
+ # The Map<T> type for {{ inner_type.canonical_name() }}.
+
+ def write_{{ canonical_type_name }}(items)
+ pack_into(4, 'l>', items.size)
+
+ items.each do |k, v|
+ write_String(k)
+ self.write_{{ inner_type.canonical_name().borrow()|class_name_rb }}(v)
+ end
+ end
+
+ {%- else -%}
+ # This type is not yet supported in the Ruby backend.
+ def write_{{ canonical_type_name }}(v)
+ raise InternalError('RustBufferStream.write() not implemented yet for {{ canonical_type_name }}')
+ end
+
+ {%- endmatch -%}
+ {%- endfor %}
+
+ private
+
+ def reserve(num_bytes)
+ if @rust_buf.len + num_bytes > @rust_buf.capacity
+ @rust_buf = RustBuffer.reserve(@rust_buf, num_bytes)
+ end
+
+ yield
+
+ @rust_buf.len += num_bytes
+ end
+
+ def pack_into(size, format, value)
+ reserve(size) do
+ @rust_buf.data.put_array_of_char @rust_buf.len, [value].pack(format).bytes
+ end
+ end
+end
+
+private_constant :RustBufferBuilder
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
new file mode 100644
index 0000000000..f48fbffad4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
@@ -0,0 +1,301 @@
+
+# Helper for structured reading of values from a RustBuffer.
+class RustBufferStream
+
+ def initialize(rbuf)
+ @rbuf = rbuf
+ @offset = 0
+ end
+
+ def remaining
+ @rbuf.len - @offset
+ end
+
+ def read(size)
+ raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len
+
+ data = @rbuf.data.get_bytes @offset, size
+
+ @offset += size
+
+ data
+ end
+
+ {% for typ in ci.iter_types() -%}
+ {%- let canonical_type_name = typ.canonical_name().borrow()|class_name_rb -%}
+ {%- match typ -%}
+
+ {% when Type::Int8 -%}
+
+ def readI8
+ unpack_from 1, 'c'
+ end
+
+ {% when Type::UInt8 -%}
+
+ def readU8
+ unpack_from 1, 'c'
+ end
+
+ {% when Type::Int16 -%}
+
+ def readI16
+ unpack_from 2, 's>'
+ end
+
+ {% when Type::UInt16 -%}
+
+ def readU16
+ unpack_from 2, 'S>'
+ end
+
+ {% when Type::Int32 -%}
+
+ def readI32
+ unpack_from 4, 'l>'
+ end
+
+ {% when Type::UInt32 -%}
+
+ def readU32
+ unpack_from 4, 'L>'
+ end
+
+ {% when Type::Int64 -%}
+
+ def readI64
+ unpack_from 8, 'q>'
+ end
+
+ {% when Type::UInt64 -%}
+
+ def readU64
+ unpack_from 8, 'Q>'
+ end
+
+ {% when Type::Float32 -%}
+
+ def readF32
+ unpack_from 4, 'g'
+ end
+
+ {% when Type::Float64 -%}
+
+ def readF64
+ unpack_from 8, 'G'
+ end
+
+ {% when Type::Boolean -%}
+
+ def readBool
+ v = unpack_from 1, 'c'
+
+ return false if v == 0
+ return true if v == 1
+
+ raise InternalError, 'Unexpected byte for Boolean type'
+ end
+
+ {% when Type::String -%}
+
+ def readString
+ size = unpack_from 4, 'l>'
+
+ raise InternalError, 'Unexpected negative string length' if size.negative?
+
+ read(size).force_encoding(Encoding::UTF_8)
+ end
+
+ {% when Type::Timestamp -%}
+ # The Timestamp type.
+ ONE_SECOND_IN_NANOSECONDS = 10**9
+
+ def read{{ canonical_type_name }}
+ seconds = unpack_from 8, 'q>'
+ nanoseconds = unpack_from 4, 'L>'
+
+ # UniFFi conventions assume that nanoseconds part has to represent nanoseconds portion of
+ # duration between epoch and the timestamp moment. Ruby `Time#tv_nsec` returns the number of
+ # nanoseconds for the subsecond part, which is sort of opposite to "duration" meaning.
+ # Hence we need to convert value returned by `Time#tv_nsec` back and forth with the following
+ # logic:
+ if seconds < 0 && nanoseconds != 0
+ # In order to get duration nsec we shift by 1 second:
+ nanoseconds = ONE_SECOND_IN_NANOSECONDS - nanoseconds
+
+ # Then we compensate 1 second shift:
+ seconds -= 1
+ end
+
+ Time.at(seconds, nanoseconds, :nanosecond, in: '+00:00').utc
+ end
+
+ {% when Type::Duration -%}
+ # The Duration type.
+
+ def read{{ canonical_type_name }}
+ seconds = unpack_from 8, 'q>'
+ nanoseconds = unpack_from 4, 'L>'
+
+ Time.at(seconds, nanoseconds, :nanosecond, in: '+00:00').utc
+ end
+
+ {% when Type::Object with (object_name) -%}
+ # The Object type {{ object_name }}.
+
+ def read{{ canonical_type_name }}
+ pointer = FFI::Pointer.new unpack_from 8, 'Q>'
+ return {{ object_name|class_name_rb }}._uniffi_allocate(pointer)
+ end
+
+ {% when Type::Enum with (enum_name) -%}
+ {%- let e = ci.get_enum_definition(enum_name).unwrap() -%}
+ # The Enum type {{ enum_name }}.
+
+ def read{{ canonical_type_name }}
+ variant = unpack_from 4, 'l>'
+ {% if e.is_flat() -%}
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}
+ return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }}
+ end
+ {%- endfor %}
+
+ raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}'
+ {%- else -%}
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}
+ {%- if variant.has_fields() %}
+ return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }}.new(
+ {%- for field in variant.fields() %}
+ self.read{{ field.type_().canonical_name().borrow()|class_name_rb }}(){% if loop.last %}{% else %},{% endif %}
+ {%- endfor %}
+ )
+ {%- else %}
+ return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }}.new
+ {% endif %}
+ end
+ {%- endfor %}
+ raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}'
+ {%- endif %}
+ end
+
+ {% when Type::Error with (error_name) -%}
+ {%- let e = ci.get_error_definition(error_name).unwrap().wrapped_enum() %}
+
+ # The Error type {{ error_name }}
+
+ def read{{ canonical_type_name }}
+ variant = unpack_from 4, 'l>'
+ {% if e.is_flat() -%}
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}
+ return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new(
+ readString()
+ )
+ end
+ {%- endfor %}
+
+ raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}'
+ {%- else -%}
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}
+ {%- if variant.has_fields() %}
+ return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new(
+ {%- for field in variant.fields() %}
+ read{{ field.type_().canonical_name().borrow()|class_name_rb }}(){% if loop.last %}{% else %},{% endif %}
+ {%- endfor %}
+ )
+ {%- else %}
+ return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new
+ {%- endif %}
+ end
+ {%- endfor %}
+
+ raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}'
+ {%- endif %}
+ end
+
+ {% when Type::Record with (record_name) -%}
+ {%- let rec = ci.get_record_definition(record_name).unwrap() -%}
+ # The Record type {{ record_name }}.
+
+ def read{{ canonical_type_name }}
+ {{ rec.name()|class_name_rb }}.new(
+ {%- for field in rec.fields() %}
+ read{{ field.type_().canonical_name().borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %}
+ {%- endfor %}
+ )
+ end
+
+ {% when Type::Optional with (inner_type) -%}
+ # The Optional<T> type for {{ inner_type.canonical_name() }}.
+
+ def read{{ canonical_type_name }}
+ flag = unpack_from 1, 'c'
+
+ if flag == 0
+ return nil
+ elsif flag == 1
+ return read{{ inner_type.canonical_name().borrow()|class_name_rb }}
+ else
+ raise InternalError, 'Unexpected flag byte for {{ canonical_type_name }}'
+ end
+ end
+
+ {% when Type::Sequence with (inner_type) -%}
+ # The Sequence<T> type for {{ inner_type.canonical_name() }}.
+
+ def read{{ canonical_type_name }}
+ count = unpack_from 4, 'l>'
+
+ raise InternalError, 'Unexpected negative sequence length' if count.negative?
+
+ items = []
+
+ count.times do
+ items.append read{{ inner_type.canonical_name().borrow()|class_name_rb }}
+ end
+
+ items
+ end
+
+ {% when Type::Map with (k, inner_type) -%}
+ # The Map<T> type for {{ inner_type.canonical_name() }}.
+
+ def read{{ canonical_type_name }}
+ count = unpack_from 4, 'l>'
+ raise InternalError, 'Unexpected negative map size' if count.negative?
+
+ items = {}
+ count.times do
+ key = readString
+ items[key] = read{{ inner_type.canonical_name().borrow()|class_name_rb }}
+ end
+
+ items
+ end
+ {%- else -%}
+ # This type is not yet supported in the Ruby backend.
+ def read{{ canonical_type_name }}
+ raise InternalError, 'RustBufferStream.read not implemented yet for {{ canonical_type_name }}'
+ end
+
+ {%- endmatch -%}
+ {%- endfor %}
+
+ def unpack_from(size, format)
+ raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len
+
+ value = @rbuf.data.get_bytes(@offset, size).unpack format
+
+ @offset += size
+
+ # TODO: verify this
+ raise 'more than one element!!!' if value.size > 1
+
+ value[0]
+ end
+end
+
+private_constant :RustBufferStream
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
new file mode 100644
index 0000000000..deed864572
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
@@ -0,0 +1,218 @@
+class RustBuffer < FFI::Struct
+ layout :capacity, :int32,
+ :len, :int32,
+ :data, :pointer
+
+ def self.alloc(size)
+ return {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_alloc().name() }}, size)
+ end
+
+ def self.reserve(rbuf, additional)
+ return {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_reserve().name() }}, rbuf, additional)
+ end
+
+ def free
+ {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_free().name() }}, self)
+ end
+
+ def capacity
+ self[:capacity]
+ end
+
+ def len
+ self[:len]
+ end
+
+ def len=(value)
+ self[:len] = value
+ end
+
+ def data
+ self[:data]
+ end
+
+ def to_s
+ "RustBuffer(capacity=#{capacity}, len=#{len}, data=#{data.read_bytes len})"
+ end
+
+ # The allocated buffer will be automatically freed if an error occurs, ensuring that
+ # we don't accidentally leak it.
+ def self.allocWithBuilder
+ builder = RustBufferBuilder.new
+
+ begin
+ yield builder
+ rescue => e
+ builder.discard
+ raise e
+ end
+ end
+
+ # The RustBuffer will be freed once the context-manager exits, ensuring that we don't
+ # leak it even if an error occurs.
+ def consumeWithStream
+ stream = RustBufferStream.new self
+
+ yield stream
+
+ raise RuntimeError, 'junk data left in buffer after consuming' if stream.remaining != 0
+ ensure
+ free
+ end
+
+ {%- for typ in ci.iter_types() -%}
+ {%- let canonical_type_name = typ.canonical_name() -%}
+ {%- match typ -%}
+
+ {% when Type::String -%}
+ # The primitive String type.
+
+ def self.allocFromString(value)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write value.encode('utf-8')
+ return builder.finalize
+ end
+ end
+
+ def consumeIntoString
+ consumeWithStream do |stream|
+ return stream.read(stream.remaining).force_encoding(Encoding::UTF_8)
+ end
+ end
+
+ {% when Type::Timestamp -%}
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Duration -%}
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Record with (record_name) -%}
+ {%- let rec = ci.get_record_definition(record_name).unwrap() -%}
+ # The Record type {{ record_name }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Enum with (enum_name) -%}
+ {%- let e = ci.get_enum_definition(enum_name).unwrap() -%}
+ # The Enum type {{ enum_name }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Optional with (inner_type) -%}
+ # The Optional<T> type for {{ inner_type.canonical_name() }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize()
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Sequence with (inner_type) -%}
+ # The Sequence<T> type for {{ inner_type.canonical_name() }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize()
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {% when Type::Map with (k, inner_type) -%}
+ # The Map<T> type for {{ inner_type.canonical_name() }}.
+
+ def self.alloc_from_{{ canonical_type_name }}(v)
+ RustBuffer.allocWithBuilder do |builder|
+ builder.write_{{ canonical_type_name }}(v)
+ return builder.finalize
+ end
+ end
+
+ def consumeInto{{ canonical_type_name }}
+ consumeWithStream do |stream|
+ return stream.read{{ canonical_type_name }}
+ end
+ end
+
+ {%- else -%}
+ {#- No code emitted for types that don't lower into a RustBuffer -#}
+ {%- endmatch -%}
+ {%- endfor %}
+end
+
+module UniFFILib
+ class ForeignBytes < FFI::Struct
+ layout :len, :int32,
+ :data, :pointer
+
+ def len
+ self[:len]
+ end
+
+ def data
+ self[:data]
+ end
+
+ def to_s
+ "ForeignBytes(len=#{len}, data=#{data.read_bytes(len)})"
+ end
+ end
+end
+
+private_constant :UniFFILib
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
new file mode 100644
index 0000000000..13214cf31b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
@@ -0,0 +1,16 @@
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%})
+ {%- call rb::coerce_args(func) %}
+ result = {% call rb::to_ffi_call(func) %}
+ return {{ "result"|lift_rb(return_type) }}
+end
+
+{% when None %}
+
+def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%})
+ {%- call rb::coerce_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
new file mode 100644
index 0000000000..c2faf63104
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
@@ -0,0 +1,73 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call` (we use `var_name_rb` in `lower_rb`)
+#}
+
+{%- macro to_ffi_call(func) -%}
+ {%- match func.throws_name() -%}
+ {%- when Some with (e) -%}
+ {{ ci.namespace()|class_name_rb }}.rust_call_with_error({{ e|class_name_rb }},
+ {%- else -%}
+ {{ ci.namespace()|class_name_rb }}.rust_call(
+ {%- endmatch -%}
+ :{{ func.ffi_func().name() }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) -%}
+ {%- match func.throws_name() -%}
+ {%- when Some with (e) -%}
+ {{ ci.namespace()|class_name_rb }}.rust_call_with_error({{ e|class_name_rb }},
+ {%- else -%}
+ {{ ci.namespace()|class_name_rb }}.rust_call(
+ {%- endmatch -%}
+ :{{ func.ffi_func().name() }},
+ {{- prefix }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg.name()|lower_rb(arg.type_().borrow()) }}
+ {%- if !loop.last %},{% endif %}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in Ruby declarations of methods, functions and constructors.
+// Note the var_name_rb and type_rb filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name_rb }}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|literal_rb }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{#-
+// Arglist as used in the UniFFILib function declarations.
+// Note unfiltered name but type_ffi filters.
+-#}
+{%- macro arg_list_ffi_decl(func) %}
+ [{%- for arg in func.arguments() -%}{{ arg.type_().borrow()|type_ffi }}, {% endfor -%} RustCallStatus.by_ref]
+{%- endmacro -%}
+
+{%- macro coerce_args(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg.name() }} = {{ arg.name()|coerce_rb(arg.type_().borrow()) -}}
+ {% endfor -%}
+{%- endmacro -%}
+
+{%- macro coerce_args_extra_indent(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg.name() }} = {{ arg.name()|coerce_rb(arg.type_().borrow()) }}
+ {%- endfor %}
+{%- endmacro -%}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb
new file mode 100644
index 0000000000..72cb60f73f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb
@@ -0,0 +1,47 @@
+# This file was autogenerated by some hot garbage in the `uniffi` crate.
+# Trust me, you don't want to mess with it!
+
+# Common helper code.
+#
+# Ideally this would live in a separate .rb file where it can be unittested etc
+# in isolation, and perhaps even published as a re-useable package.
+#
+# However, it's important that the detils of how this helper code works (e.g. the
+# way that different builtin types are passed across the FFI) exactly match what's
+# expected by the rust code on the other side of the interface. In practice right
+# now that means coming from the exact some version of `uniffi` that was used to
+# compile the rust component. The easiest way to ensure this is to bundle the Ruby
+# helpers directly inline like we're doing here.
+
+require 'ffi'
+
+module {{ ci.namespace()|class_name_rb }}
+ {% include "RustBufferTemplate.rb" %}
+ {% include "RustBufferStream.rb" %}
+ {% include "RustBufferBuilder.rb" %}
+
+ # Error definitions
+ {% include "ErrorTemplate.rb" %}
+
+ {% include "NamespaceLibraryTemplate.rb" %}
+
+ # Public interface members begin here.
+
+ {% for e in ci.enum_definitions() %}
+ {% include "EnumTemplate.rb" %}
+ {%- endfor -%}
+
+ {%- for rec in ci.record_definitions() %}
+ {% include "RecordTemplate.rb" %}
+ {% endfor %}
+
+ {% for func in ci.function_definitions() %}
+ {% include "TopLevelFunctionTemplate.rb" %}
+ {% endfor %}
+
+ {% for obj in ci.object_definitions() %}
+ {% include "ObjectTemplate.rb" %}
+ {% endfor %}
+end
+
+{% import "macros.rb" as rb %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
new file mode 100644
index 0000000000..5c445bfae5
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
@@ -0,0 +1,62 @@
+/* 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 anyhow::{bail, Context, Result};
+use camino::Utf8Path;
+use std::env;
+use std::ffi::OsString;
+use std::process::{Command, Stdio};
+use uniffi_testing::UniFFITestHelper;
+
+/// Run Ruby tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_cdylibs_to_out_dir")?;
+ generate_sources(&test_helper.cdylib_path()?, &out_dir, &test_helper)
+ .context("generate_sources")?;
+
+ let rubypath = env::var_os("RUBYLIB").unwrap_or_else(|| OsString::from(""));
+ let rubypath = env::join_paths(
+ env::split_paths(&rubypath).chain(vec![out_dir.to_path_buf().into_std_path_buf()]),
+ )?;
+
+ let status = Command::new("ruby")
+ .current_dir(out_dir)
+ .env("RUBYLIB", rubypath)
+ .arg(script_path)
+ .stderr(Stdio::inherit())
+ .stdout(Stdio::inherit())
+ .spawn()
+ .context("Failed to spawn `ruby` when running script")?
+ .wait()
+ .context("Failed to wait for `ruby` when running script")?;
+ if !status.success() {
+ bail!("running `ruby` failed");
+ }
+ Ok(())
+}
+
+fn generate_sources(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+) -> Result<()> {
+ for source in test_helper.get_compile_sources()? {
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["ruby"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+ Ok(())
+}
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
new file mode 100644
index 0000000000..828a8823ee
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs
@@ -0,0 +1,25 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct CallbackInterfaceCodeType {
+ id: String,
+}
+
+impl CallbackInterfaceCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for CallbackInterfaceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!("CallbackInterface{}", self.type_label(oracle))
+ }
+}
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
new file mode 100644
index 0000000000..f91be9ce54
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
@@ -0,0 +1,98 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal, TypeIdentifier};
+
+pub struct OptionalCodeType {
+ inner: TypeIdentifier,
+}
+
+impl OptionalCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for OptionalCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}?", oracle.find(&self.inner).type_label(oracle))
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!("Option{}", oracle.find(&self.inner).canonical_name(oracle))
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::Null => "nil".into(),
+ _ => oracle.find(&self.inner).literal(oracle, literal),
+ }
+ }
+}
+
+pub struct SequenceCodeType {
+ inner: TypeIdentifier,
+}
+
+impl SequenceCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for SequenceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!("[{}]", oracle.find(&self.inner).type_label(oracle))
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Sequence{}",
+ oracle.find(&self.inner).canonical_name(oracle)
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptySequence => "[]".into(),
+ _ => unreachable!(),
+ }
+ }
+}
+
+pub struct MapCodeType {
+ key: TypeIdentifier,
+ value: TypeIdentifier,
+}
+
+impl MapCodeType {
+ pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self {
+ Self { key, value }
+ }
+}
+
+impl CodeType for MapCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "[{}: {}]",
+ oracle.find(&self.key).type_label(oracle),
+ oracle.find(&self.value).type_label(oracle)
+ )
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Dictionary{}{}",
+ oracle.find(&self.key).canonical_name(oracle),
+ oracle.find(&self.value).canonical_name(oracle)
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptyMap => "[:]".into(),
+ _ => unreachable!(),
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs
new file mode 100644
index 0000000000..bf1806e5e2
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs
@@ -0,0 +1,25 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct CustomCodeType {
+ name: String,
+}
+
+impl CustomCodeType {
+ pub fn new(name: String) -> Self {
+ CustomCodeType { name }
+ }
+}
+
+impl CodeType for CustomCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs
new file mode 100644
index 0000000000..04d8422f6a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs
@@ -0,0 +1,33 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct EnumCodeType {
+ id: String,
+}
+
+impl EnumCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for EnumCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ if let Literal::Enum(v, _) = literal {
+ format!(".{}", oracle.enum_variant_name(v))
+ } else {
+ unreachable!();
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs
new file mode 100644
index 0000000000..4219f24c39
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/error.rs
@@ -0,0 +1,25 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct ErrorCodeType {
+ id: String,
+}
+
+impl ErrorCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ErrorCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+}
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
new file mode 100644
index 0000000000..c9a925cdaa
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
@@ -0,0 +1,35 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct ExternalCodeType {
+ name: String,
+}
+
+impl ExternalCodeType {
+ pub fn new(name: String) -> Self {
+ ExternalCodeType { name }
+ }
+}
+
+impl CodeType for ExternalCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ // lower and lift need to call public function which were generated for
+ // the original types.
+ fn lower(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}_lower", self.ffi_converter_name(oracle))
+ }
+
+ fn lift(&self, oracle: &dyn CodeOracle) -> String {
+ format!("{}_lift", self.ffi_converter_name(oracle))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs
new file mode 100644
index 0000000000..4f57734934
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs
@@ -0,0 +1,29 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct TimestampCodeType;
+
+impl CodeType for TimestampCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "Date".into()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ "Timestamp".into()
+ }
+}
+
+pub struct DurationCodeType;
+
+impl CodeType for DurationCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "TimeInterval".into()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ "Duration".into()
+ }
+}
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
new file mode 100644
index 0000000000..abbe0b3539
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
@@ -0,0 +1,476 @@
+/* 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 std::borrow::Borrow;
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap, HashSet};
+
+use anyhow::{Context, Result};
+use askama::Template;
+use heck::{ToLowerCamelCase, ToUpperCamelCase};
+use serde::{Deserialize, Serialize};
+
+use super::Bindings;
+use crate::backend::{CodeOracle, CodeType, TemplateExpression, TypeIdentifier};
+use crate::interface::*;
+use crate::MergeWith;
+
+mod callback_interface;
+mod compounds;
+mod custom;
+mod enum_;
+mod error;
+mod external;
+mod miscellany;
+mod object;
+mod primitives;
+mod record;
+
+/// Config options for the caller to customize the generated Swift.
+///
+/// Note that this can only be used to control details of the Swift *that do not affect the underlying component*,
+/// since the details of the underlying component are entirely determined by the `ComponentInterface`.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ cdylib_name: Option<String>,
+ module_name: Option<String>,
+ ffi_module_name: Option<String>,
+ ffi_module_filename: Option<String>,
+ generate_module_map: Option<bool>,
+ omit_argument_labels: Option<bool>,
+ #[serde(default)]
+ custom_types: HashMap<String, CustomTypeConfig>,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub struct CustomTypeConfig {
+ imports: Option<Vec<String>>,
+ type_name: Option<String>,
+ into_custom: TemplateExpression,
+ from_custom: TemplateExpression,
+}
+
+impl Config {
+ /// The name of the Swift module containing the high-level foreign-language bindings.
+ pub fn module_name(&self) -> String {
+ match self.module_name.as_ref() {
+ Some(name) => name.clone(),
+ None => "uniffi".into(),
+ }
+ }
+
+ /// The name of the lower-level C module containing the FFI declarations.
+ pub fn ffi_module_name(&self) -> String {
+ match self.ffi_module_name.as_ref() {
+ Some(name) => name.clone(),
+ None => format!("{}FFI", self.module_name()),
+ }
+ }
+
+ /// The filename stem for the lower-level C module containing the FFI declarations.
+ pub fn ffi_module_filename(&self) -> String {
+ match self.ffi_module_filename.as_ref() {
+ Some(name) => name.clone(),
+ None => self.ffi_module_name(),
+ }
+ }
+
+ /// The name of the `.modulemap` file for the lower-level C module with FFI declarations.
+ pub fn modulemap_filename(&self) -> String {
+ format!("{}.modulemap", self.ffi_module_filename())
+ }
+
+ /// The name of the `.h` file for the lower-level C module with FFI declarations.
+ pub fn header_filename(&self) -> String {
+ format!("{}.h", self.ffi_module_filename())
+ }
+
+ /// The name of the compiled Rust library containing the FFI implementation.
+ pub fn cdylib_name(&self) -> String {
+ if let Some(cdylib_name) = &self.cdylib_name {
+ cdylib_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+
+ /// Whether to generate a `.modulemap` file for the lower-level C module with FFI declarations.
+ pub fn generate_module_map(&self) -> bool {
+ self.generate_module_map.unwrap_or(true)
+ }
+
+ /// Whether to omit argument labels in Swift function definitions.
+ pub fn omit_argument_labels(&self) -> bool {
+ self.omit_argument_labels.unwrap_or(false)
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ module_name: Some(ci.namespace().into()),
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ ..Default::default()
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ module_name: self.module_name.merge_with(&other.module_name),
+ ffi_module_name: self.ffi_module_name.merge_with(&other.ffi_module_name),
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ ffi_module_filename: self
+ .ffi_module_filename
+ .merge_with(&other.ffi_module_filename),
+ generate_module_map: self
+ .generate_module_map
+ .merge_with(&other.generate_module_map),
+ omit_argument_labels: self
+ .omit_argument_labels
+ .merge_with(&other.omit_argument_labels),
+ custom_types: self.custom_types.merge_with(&other.custom_types),
+ }
+ }
+}
+
+/// Generate UniFFI component bindings for Swift, as strings in memory.
+///
+pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<Bindings> {
+ let header = BridgingHeader::new(config, ci)
+ .render()
+ .context("failed to render Swift bridging header")?;
+ let library = SwiftWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render Swift library")?;
+ let modulemap = if config.generate_module_map() {
+ Some(
+ ModuleMap::new(config, ci)
+ .render()
+ .context("failed to render Swift modulemap")?,
+ )
+ } else {
+ None
+ };
+ Ok(Bindings {
+ library,
+ header,
+ modulemap,
+ })
+}
+
+/// Renders Swift helper code for all types
+///
+/// This template is a bit different than others in that it stores internal state from the render
+/// process. Make sure to only call `render()` once.
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "Types.swift")]
+pub struct TypeRenderer<'a> {
+ config: &'a Config,
+ ci: &'a ComponentInterface,
+ // Track included modules for the `include_once()` macro
+ include_once_names: RefCell<HashSet<String>>,
+ // Track imports added with the `add_import()` macro
+ imports: RefCell<BTreeSet<String>>,
+}
+
+impl<'a> TypeRenderer<'a> {
+ fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self {
+ Self {
+ config,
+ ci,
+ include_once_names: RefCell::new(HashSet::new()),
+ imports: RefCell::new(BTreeSet::new()),
+ }
+ }
+
+ // The following methods are used by the `Types.kt` macros.
+
+ // Helper for the including a template, but only once.
+ //
+ // The first time this is called with a name it will return true, indicating that we should
+ // include the template. Subsequent calls will return false.
+ fn include_once_check(&self, name: &str) -> bool {
+ self.include_once_names
+ .borrow_mut()
+ .insert(name.to_string())
+ }
+
+ // Helper to add an import statement
+ //
+ // Call this inside your template to cause an import statement to be added at the top of the
+ // file. Imports will be sorted and de-deuped.
+ //
+ // Returns an empty string so that it can be used inside an askama `{{ }}` block.
+ fn add_import(&self, name: &str) -> &str {
+ self.imports.borrow_mut().insert(name.to_owned());
+ ""
+ }
+}
+
+/// Template for generating the `.h` file that defines the low-level C FFI.
+///
+/// This file defines only the low-level structs and functions that are exposed
+/// by the compiled Rust code. It gets wrapped into a higher-level API by the
+/// code from [`SwiftWrapper`].
+#[derive(Template)]
+#[template(syntax = "c", escape = "none", path = "BridgingHeaderTemplate.h")]
+pub struct BridgingHeader<'config, 'ci> {
+ _config: &'config Config,
+ ci: &'ci ComponentInterface,
+}
+
+impl<'config, 'ci> BridgingHeader<'config, 'ci> {
+ pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self {
+ Self {
+ _config: config,
+ ci,
+ }
+ }
+}
+
+/// Template for generating the `.modulemap` file that exposes the low-level C FFI.
+///
+/// This file defines how the low-level C FFI from [`BridgingHeader`] gets exposed
+/// as a Swift module that can be called by other Swift code. In our case, its only
+/// job is to define the *name* of the Swift module that will contain the FFI functions
+/// so that it can be imported by the higher-level code in from [`SwiftWrapper`].
+#[derive(Template)]
+#[template(syntax = "c", escape = "none", path = "ModuleMapTemplate.modulemap")]
+pub struct ModuleMap<'config, 'ci> {
+ config: &'config Config,
+ _ci: &'ci ComponentInterface,
+}
+
+impl<'config, 'ci> ModuleMap<'config, 'ci> {
+ pub fn new(config: &'config Config, _ci: &'ci ComponentInterface) -> Self {
+ Self { config, _ci }
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "swift", escape = "none", path = "wrapper.swift")]
+pub struct SwiftWrapper<'a> {
+ config: Config,
+ ci: &'a ComponentInterface,
+ type_helper_code: String,
+ type_imports: BTreeSet<String>,
+}
+impl<'a> SwiftWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ let type_renderer = TypeRenderer::new(&config, ci);
+ let type_helper_code = type_renderer.render().unwrap();
+ let type_imports = type_renderer.imports.into_inner();
+ Self {
+ config,
+ ci,
+ type_helper_code,
+ type_imports,
+ }
+ }
+
+ pub fn imports(&self) -> Vec<String> {
+ self.type_imports.iter().cloned().collect()
+ }
+
+ pub fn initialization_fns(&self) -> Vec<String> {
+ self.ci
+ .iter_types()
+ .into_iter()
+ .filter_map(|t| t.initialization_fn(&SwiftCodeOracle))
+ .collect()
+ }
+}
+
+#[derive(Clone)]
+pub struct SwiftCodeOracle;
+
+impl SwiftCodeOracle {
+ // Map `Type` instances to a `Box<dyn CodeType>` for that type.
+ //
+ // There is a companion match in `templates/Types.swift` which performs a similar function for the
+ // template code.
+ //
+ // - When adding additional types here, make sure to also add a match arm to the `Types.swift` template.
+ // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+ match type_ {
+ Type::UInt8 => Box::new(primitives::UInt8CodeType),
+ Type::Int8 => Box::new(primitives::Int8CodeType),
+ Type::UInt16 => Box::new(primitives::UInt16CodeType),
+ Type::Int16 => Box::new(primitives::Int16CodeType),
+ Type::UInt32 => Box::new(primitives::UInt32CodeType),
+ Type::Int32 => Box::new(primitives::Int32CodeType),
+ Type::UInt64 => Box::new(primitives::UInt64CodeType),
+ Type::Int64 => Box::new(primitives::Int64CodeType),
+ Type::Float32 => Box::new(primitives::Float32CodeType),
+ Type::Float64 => Box::new(primitives::Float64CodeType),
+ Type::Boolean => Box::new(primitives::BooleanCodeType),
+ Type::String => Box::new(primitives::StringCodeType),
+
+ Type::Timestamp => Box::new(miscellany::TimestampCodeType),
+ Type::Duration => Box::new(miscellany::DurationCodeType),
+
+ Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+ Type::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+ Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
+ Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
+ Type::CallbackInterface(id) => {
+ Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
+ }
+
+ Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)),
+ Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)),
+ Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)),
+ Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
+ Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling create_code_type")
+ }
+ }
+ }
+}
+
+impl CodeOracle for SwiftCodeOracle {
+ fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+ self.create_code_type(type_.clone())
+ }
+
+ /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, nm: &str) -> String {
+ nm.to_string().to_upper_camel_case()
+ }
+
+ /// Get the idiomatic Swift rendering of a function name.
+ fn fn_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of a variable name.
+ fn var_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of an individual enum variant.
+ fn enum_variant_name(&self, nm: &str) -> String {
+ format!("`{}`", nm.to_string().to_lower_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of an exception name.
+ fn error_name(&self, nm: &str) -> String {
+ format!("`{}`", self.class_name(nm))
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::Int8 => "int8_t".into(),
+ FfiType::UInt8 => "uint8_t".into(),
+ FfiType::Int16 => "int16_t".into(),
+ FfiType::UInt16 => "uint16_t".into(),
+ FfiType::Int32 => "int32_t".into(),
+ FfiType::UInt32 => "uint32_t".into(),
+ FfiType::Int64 => "int64_t".into(),
+ FfiType::UInt64 => "uint64_t".into(),
+ FfiType::Float32 => "float".into(),
+ FfiType::Float64 => "double".into(),
+ FfiType::RustArcPtr(_) => "void*_Nonnull".into(),
+ FfiType::RustBuffer(_) => "RustBuffer".into(),
+ FfiType::ForeignBytes => "ForeignBytes".into(),
+ FfiType::ForeignCallback => "ForeignCallback _Nonnull".to_string(),
+ }
+ }
+}
+
+pub mod filters {
+ use super::*;
+
+ fn oracle() -> &'static SwiftCodeOracle {
+ &SwiftCodeOracle
+ }
+
+ pub fn type_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.type_label(oracle))
+ }
+
+ pub fn canonical_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.canonical_name(oracle))
+ }
+
+ pub fn ffi_converter_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.ffi_converter_name(oracle()))
+ }
+
+ pub fn lower_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.lower(oracle()))
+ }
+
+ pub fn write_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.write(oracle()))
+ }
+
+ pub fn lift_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.lift(oracle()))
+ }
+
+ pub fn read_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(codetype.read(oracle()))
+ }
+
+ pub fn literal_swift(
+ literal: &Literal,
+ codetype: &impl CodeType,
+ ) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.literal(oracle, literal))
+ }
+
+ /// Get the Swift syntax for representing a given low-level `FfiType`.
+ pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_type_label(type_))
+ }
+
+ /// Get the type that a type is lowered into. This is subtly different than `type_ffi`, see
+ /// #1106 for details
+ pub fn type_ffi_lowered(ffi_type: &FfiType) -> Result<String, askama::Error> {
+ Ok(match ffi_type {
+ FfiType::Int8 => "Int8".into(),
+ FfiType::UInt8 => "UInt8".into(),
+ FfiType::Int16 => "Int16".into(),
+ FfiType::UInt16 => "UInt16".into(),
+ FfiType::Int32 => "Int32".into(),
+ FfiType::UInt32 => "UInt32".into(),
+ FfiType::Int64 => "Int64".into(),
+ FfiType::UInt64 => "UInt64".into(),
+ FfiType::Float32 => "float".into(),
+ FfiType::Float64 => "double".into(),
+ FfiType::RustArcPtr(_) => "void*_Nonnull".into(),
+ FfiType::RustBuffer(_) => "RustBuffer".into(),
+ FfiType::ForeignBytes => "ForeignBytes".into(),
+ FfiType::ForeignCallback => "ForeignCallback _Nonnull".to_string(),
+ })
+ }
+
+ /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).
+ pub fn class_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().class_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of a function name.
+ pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().fn_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of a variable name.
+ pub fn var_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().var_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of an individual enum variant.
+ pub fn enum_variant_swift(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().enum_variant_name(nm))
+ }
+}
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
new file mode 100644
index 0000000000..d227f8e7cd
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs
@@ -0,0 +1,25 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct ObjectCodeType {
+ id: String,
+}
+
+impl ObjectCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ObjectCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+}
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
new file mode 100644
index 0000000000..9989e0a421
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
@@ -0,0 +1,87 @@
+/* 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 crate::backend::{CodeOracle, CodeType, Literal};
+use crate::interface::{types::Type, Radix};
+use paste::paste;
+
+fn render_literal(oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ fn typed_number(oracle: &dyn CodeOracle, type_: &Type, num_str: String) -> String {
+ match type_ {
+ // special case Int32.
+ Type::Int32 => num_str,
+ // otherwise use constructor e.g. UInt8(x)
+ Type::Int8
+ | Type::UInt8
+ | Type::Int16
+ | Type::UInt16
+ | Type::UInt32
+ | Type::Int64
+ | Type::UInt64
+ | Type::Float32
+ | Type::Float64 =>
+ // XXX we should pass in the codetype itself.
+ {
+ format!("{}({num_str})", oracle.find(type_).type_label(oracle))
+ }
+ _ => panic!("Unexpected literal: {num_str} is not a number"),
+ }
+ }
+
+ match literal {
+ Literal::Boolean(v) => format!("{v}"),
+ Literal::String(s) => format!("\"{s}\""),
+ Literal::Int(i, radix, type_) => typed_number(
+ oracle,
+ type_,
+ match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ ),
+ Literal::UInt(i, radix, type_) => typed_number(
+ oracle,
+ type_,
+ match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ ),
+ Literal::Float(string, type_) => typed_number(oracle, type_, string.clone()),
+ _ => unreachable!("Literal"),
+ }
+}
+
+macro_rules! impl_code_type_for_primitive {
+ ($T:ty, $class_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, &literal)
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_primitive!(BooleanCodeType, "Bool");
+impl_code_type_for_primitive!(StringCodeType, "String");
+impl_code_type_for_primitive!(Int8CodeType, "Int8");
+impl_code_type_for_primitive!(Int16CodeType, "Int16");
+impl_code_type_for_primitive!(Int32CodeType, "Int32");
+impl_code_type_for_primitive!(Int64CodeType, "Int64");
+impl_code_type_for_primitive!(UInt8CodeType, "UInt8");
+impl_code_type_for_primitive!(UInt16CodeType, "UInt16");
+impl_code_type_for_primitive!(UInt32CodeType, "UInt32");
+impl_code_type_for_primitive!(UInt64CodeType, "UInt64");
+impl_code_type_for_primitive!(Float32CodeType, "Float");
+impl_code_type_for_primitive!(Float64CodeType, "Double");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs
new file mode 100644
index 0000000000..0943bcaada
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs
@@ -0,0 +1,25 @@
+/* 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 crate::backend::{CodeOracle, CodeType};
+
+pub struct RecordCodeType {
+ id: String,
+}
+
+impl RecordCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for RecordCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs
new file mode 100644
index 0000000000..5fd1c404cb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs
@@ -0,0 +1,99 @@
+/* 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/. */
+
+//! # Swift bindings backend for UniFFI
+//!
+//! This module generates Swift bindings from a [`ComponentInterface`] definition,
+//! using Swift's builtin support for loading C header files.
+//!
+//! Conceptually, the generated bindings are split into two Swift modules, one for the low-level
+//! C FFI layer and one for the higher-level Swift bindings. For a UniFFI component named "example"
+//! we generate:
+//!
+//! * A C header file `exampleFFI.h` declaring the low-level structs and functions for calling
+//! into Rust, along with a corresponding `exampleFFI.modulemap` to expose them to Swift.
+//!
+//! * A Swift source file `example.swift` that imports the `exampleFFI` module and wraps it
+//! to provide the higher-level Swift API.
+//!
+//! Most of the concepts in a [`ComponentInterface`] have an obvious counterpart in Swift,
+//! with the details documented in inline comments where appropriate.
+//!
+//! To handle lifting/lowering/serializing types across the FFI boundary, the Swift code
+//! defines a `protocol ViaFfi` that is analogous to the `uniffi::ViaFfi` Rust trait.
+//! Each type that can traverse the FFI conforms to the `ViaFfi` protocol, which specifies:
+//!
+//! * The corresponding low-level type.
+//! * How to lift from and lower into into that type.
+//! * How to read from and write into a byte buffer.
+//!
+
+use std::{io::Write, process::Command};
+
+use anyhow::Result;
+use camino::Utf8Path;
+use fs_err::File;
+
+pub mod gen_swift;
+pub use gen_swift::{generate_bindings, Config};
+mod test;
+
+use super::super::interface::ComponentInterface;
+pub use test::run_test;
+
+/// The Swift bindings generated from a [`ComponentInterface`].
+///
+pub struct Bindings {
+ /// The contents of the generated `.swift` file, as a string.
+ library: String,
+ /// The contents of the generated `.h` file, as a string.
+ header: String,
+ /// The contents of the generated `.modulemap` file, as a string.
+ modulemap: Option<String>,
+}
+
+/// Write UniFFI component bindings for Swift as files on disk.
+///
+/// Unlike other target languages, binding to Rust code from Swift involves more than just
+/// generating a `.swift` file. We also need to produce a `.h` file with the C-level API
+/// declarations, and a `.modulemap` file to tell Swift how to use it.
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let Bindings {
+ header,
+ library,
+ modulemap,
+ } = generate_bindings(config, ci)?;
+
+ let source_file = out_dir.join(format!("{}.swift", config.module_name()));
+ let mut l = File::create(&source_file)?;
+ write!(l, "{library}")?;
+
+ let mut h = File::create(out_dir.join(config.header_filename()))?;
+ write!(h, "{header}")?;
+
+ if let Some(modulemap) = modulemap {
+ let mut m = File::create(out_dir.join(config.modulemap_filename()))?;
+ write!(m, "{modulemap}")?;
+ }
+
+ if try_format_code {
+ if let Err(e) = Command::new("swiftformat")
+ .arg(source_file.as_str())
+ .output()
+ {
+ println!(
+ "Warning: Unable to auto-format {} using swiftformat: {:?}",
+ source_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift
new file mode 100644
index 0000000000..465e519628
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift
@@ -0,0 +1,20 @@
+fileprivate struct FfiConverterBool : FfiConverter {
+ typealias FfiType = Int8
+ typealias SwiftType = Bool
+
+ public static func lift(_ value: Int8) throws -> Bool {
+ return value != 0
+ }
+
+ public static func lower(_ value: Bool) -> Int8 {
+ return value ? 1 : 0
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Bool, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
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
new file mode 100644
index 0000000000..50b95d96e8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
@@ -0,0 +1,55 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+// The following structs are used to implement the lowest level
+// of the FFI, and thus useful to multiple uniffied crates.
+// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
+#ifdef UNIFFI_SHARED_H
+ // We also try to prevent mixing versions of shared uniffi header structs.
+ // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
+ #ifndef UNIFFI_SHARED_HEADER_V4
+ #error Combining helper code from multiple versions of uniffi is not supported
+ #endif // ndef UNIFFI_SHARED_HEADER_V4
+#else
+#define UNIFFI_SHARED_H
+#define UNIFFI_SHARED_HEADER_V4
+// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
+// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
+
+typedef struct RustBuffer
+{
+ int32_t capacity;
+ int32_t len;
+ uint8_t *_Nullable data;
+} RustBuffer;
+
+typedef int32_t (*ForeignCallback)(uint64_t, int32_t, RustBuffer, RustBuffer *_Nonnull);
+
+typedef struct ForeignBytes
+{
+ int32_t len;
+ const uint8_t *_Nullable data;
+} ForeignBytes;
+
+// Error definitions
+typedef struct RustCallStatus {
+ int8_t code;
+ RustBuffer errorBuf;
+} RustCallStatus;
+
+// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
+// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
+#endif // def UNIFFI_SHARED_H
+
+{% for func in ci.iter_ffi_function_definitions() -%}
+ {%- match func.return_type() -%}{%- when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}(
+ {% call swift::arg_list_ffi_decl(func) %}
+ );
+{% endfor -%}
+
+{% import "macros.swift" as swift %}
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
new file mode 100644
index 0000000000..9ab9bdbe48
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
@@ -0,0 +1,60 @@
+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
new file mode 100644
index 0000000000..7842511a83
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
@@ -0,0 +1,154 @@
+{%- let cbi = ci.get_callback_interface_definition(name).unwrap() %}
+{%- 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, args: RustBuffer, 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 }}, _ args: RustBuffer) throws -> RustBuffer {
+ defer { args.deallocate() }
+ {#- Unpacking args from the RustBuffer #}
+ {%- if meth.arguments().len() != 0 -%}
+ {#- Calling the concrete callback object #}
+
+ var reader = createReader(data: Data(rustBuffer: args))
+ {% if meth.return_type().is_some() %}let result = {% endif -%}
+ {% 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 -%}
+ )
+ {% else %}
+ {% if meth.return_type().is_some() %}let result = {% endif -%}
+ {% if meth.throws() %}try {% endif -%}
+ swiftCallbackInterface.{{ meth.name()|fn_name }}()
+ {% endif -%}
+
+ {#- Packing up the return value into a RustBuffer #}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ var writer = [UInt8]()
+ {{ return_type|write_fn }}(result, into: &writer)
+ return RustBuffer(bytes: writer)
+ {%- else -%}
+ return RustBuffer()
+ {% endmatch -%}
+ // TODO catch errors and report them back to Rust.
+ // https://github.com/mozilla/uniffi-rs/issues/351
+
+ }
+ {% endfor %}
+
+ 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 -1
+ }
+
+ switch method {
+ case IDX_CALLBACK_FREE:
+ {{ ffi_converter_name }}.drop(handle: handle)
+ // No return value.
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 0
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
+ case {{ loop.index }}:
+ do {
+ out_buf.pointee = try {{ method_name }}(cb, args)
+ // Value written to out buffer.
+ // See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 1
+ {%- match meth.throws_type() %}
+ {%- when Some(error_type) %}
+ } catch let error as {{ error_type|type_name }} {
+ out_buf.pointee = {{ error_type|lower_fn }}(error)
+ return -2
+ {%- else %}
+ {%- endmatch %}
+ } catch let error {
+ out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
+ return -1
+ }
+ {% 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/src/ffi/foreigncallbacks.rs`
+ return -1
+ }
+ }
+
+// FfiConverter protocol for callback interfaces
+fileprivate struct {{ ffi_converter_name }} {
+ // Initialize our callback method with the scaffolding code
+ private static var callbackInitialized = false
+ private static func initCallback() {
+ try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in
+ {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)
+ }
+ }
+ private static func ensureCallbackinitialized() {
+ if !callbackInitialized {
+ initCallback()
+ callbackInitialized = true
+ }
+ }
+
+ static func drop(handle: UniFFICallbackHandle) {
+ handleMap.remove(handle: handle)
+ }
+
+ private static var handleMap = UniFFICallbackHandleMap<{{ 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
+
+ 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 read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
+ ensureCallbackinitialized();
+ let handle: UniFFICallbackHandle = try readInt(&buf)
+ return try lift(handle)
+ }
+
+ public static func lower(_ v: SwiftType) -> UniFFICallbackHandle {
+ ensureCallbackinitialized();
+ 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/CustomType.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift
new file mode 100644
index 0000000000..cb63f0b97b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift
@@ -0,0 +1,86 @@
+{%- let ffi_type_name=builtin.ffi_type().borrow()|type_ffi_lowered %}
+{%- match config.custom_types.get(name.as_str()) %}
+{%- when None %}
+{#- No config, just forward all methods to our builtin type #}
+/**
+ * Typealias from the type name used in the UDL file to the builtin type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ */
+public typealias {{ name }} = {{ builtin|type_name }}
+public struct FfiConverterType{{ name }}: FfiConverter {
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} {
+ return try {{ builtin|read_fn }}(from: &buf)
+ }
+
+ public static func write(_ value: {{ name }}, into buf: inout [UInt8]) {
+ return {{ builtin|write_fn }}(value, into: &buf)
+ }
+
+ public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} {
+ return try {{ builtin|lift_fn }}(value)
+ }
+
+ public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} {
+ return {{ builtin|lower_fn }}(value)
+ }
+}
+
+{%- when Some with (config) %}
+
+{# When the config specifies a different type name, create a typealias for it #}
+{%- match config.type_name %}
+{%- when Some with (concrete_type_name) %}
+/**
+ * Typealias from the type name used in the UDL file to the custom type. This
+ * is needed because the UDL type name is used in function/method signatures.
+ */
+public typealias {{ name }} = {{ concrete_type_name }}
+{%- else %}
+{%- endmatch %}
+
+{%- match config.imports %}
+{%- when Some(imports) %}
+{%- for import_name in imports %}
+{{ self.add_import(import_name) }}
+{%- endfor %}
+{%- else %}
+{%- endmatch %}
+
+public struct FfiConverterType{{ name }}: FfiConverter {
+ {#- Custom type config supplied, use it to convert the builtin type #}
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} {
+ let builtinValue = try {{ builtin|read_fn }}(from: &buf)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ public static func write(_ value: {{ name }}, into buf: inout [UInt8]) {
+ let builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|write_fn }}(builtinValue, into: &buf)
+ }
+
+ public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} {
+ let builtinValue = try {{ builtin|lift_fn }}(value)
+ return {{ config.into_custom.render("builtinValue") }}
+ }
+
+ public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} {
+ let builtinValue = {{ config.from_custom.render("value") }}
+ return {{ builtin|lower_fn }}(builtinValue)
+ }
+}
+
+{#
+We always write these public functions just incase the type is used as
+an external type by another crate.
+#}
+public func FfiConverterType{{ name }}_lift(_ buf: RustBuffer) throws -> {{ name }} {
+ return try FfiConverterType{{ name }}.lift(buf)
+}
+
+public func FfiConverterType{{ name }}_lower(_ value: {{ name }}) -> RustBuffer {
+ return FfiConverterType{{ name }}.lower(value)
+}
+
+{%- endmatch %}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift
new file mode 100644
index 0000000000..c2aa49e9d1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift
@@ -0,0 +1,24 @@
+fileprivate struct FfiConverterDuration: FfiConverterRustBuffer {
+ typealias SwiftType = TimeInterval
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TimeInterval {
+ let seconds: UInt64 = try readInt(&buf)
+ let nanoseconds: UInt32 = try readInt(&buf)
+ return Double(seconds) + (Double(nanoseconds) / 1.0e9)
+ }
+
+ public static func write(_ value: TimeInterval, into buf: inout [UInt8]) {
+ if value.rounded(.down) > Double(Int64.max) {
+ fatalError("Duration overflow, exceeds max bounds supported by Uniffi")
+ }
+
+ if value < 0 {
+ fatalError("Invalid duration, must be non-negative")
+ }
+
+ let seconds = UInt64(value)
+ let nanoseconds = UInt32((value - Double(seconds)) * 1.0e9)
+ writeInt(&buf, seconds)
+ writeInt(&buf, nanoseconds)
+ }
+}
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
new file mode 100644
index 0000000000..74f0a3effe
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
@@ -0,0 +1,60 @@
+// Note that we don't yet support `indirect` for enums.
+// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+public enum {{ type_name }} {
+ {% for variant in e.variants() %}
+ case {{ variant.name()|enum_variant_swift }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
+ {% endfor %}
+}
+
+public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ typealias SwiftType = {{ type_name }}
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
+ let variant: Int32 = try readInt(&buf)
+ switch variant {
+ {% for variant in e.variants() %}
+ case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift }}{% if variant.has_fields() %}(
+ {%- for field in variant.fields() %}
+ {{ field.name()|var_name }}: try {{ field|read_fn }}(from: &buf)
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}
+ ){%- endif %}
+ {% endfor %}
+ default: throw UniffiInternalError.unexpectedEnumCase
+ }
+ }
+
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ switch value {
+ {% for variant in e.variants() %}
+ {% if variant.has_fields() %}
+ case let .{{ variant.name()|enum_variant_swift }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- 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)
+ {% endfor -%}
+ {% else %}
+ case .{{ variant.name()|enum_variant_swift }}:
+ writeInt(&buf, Int32({{ loop.index }}))
+ {% endif %}
+ {%- endfor %}
+ }
+ }
+}
+
+{#
+We always write these public functions just in case the enum is used as
+an external type by another crate.
+#}
+public func {{ ffi_converter_name }}_lift(_ buf: RustBuffer) throws -> {{ type_name }} {
+ return try {{ ffi_converter_name }}.lift(buf)
+}
+
+public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuffer {
+ return {{ ffi_converter_name }}.lower(value)
+}
+
+{% if !contains_object_references %}
+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
new file mode 100644
index 0000000000..b2c4bfe492
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
@@ -0,0 +1,83 @@
+{%- let e = ci.get_error_definition(name).unwrap() %}
+public enum {{ type_name }} {
+
+ {% if e.is_flat() %}
+ {% for variant in e.variants() %}
+ // Simple error enums only carry a message
+ 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 -%}
+ {% endfor %}
+
+ {%- endif %}
+}
+
+public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ typealias SwiftType = {{ type_name }}
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
+ let variant: Int32 = try readInt(&buf)
+ switch variant {
+
+ {% if e.is_flat() %}
+
+ {% for variant in e.variants() %}
+ case {{ loop.index }}: return .{{ variant.name()|class_name }}(
+ message: try {{ Type::String.borrow()|read_fn }}(from: &buf)
+ )
+ {% endfor %}
+
+ {% else %}
+
+ {% for variant in e.variants() %}
+ case {{ loop.index }}: return .{{ variant.name()|class_name }}{% if variant.has_fields() -%}(
+ {% for field in variant.fields() -%}
+ {{ field.name()|var_name }}: try {{ field|read_fn }}(from: &buf)
+ {%- if !loop.last %}, {% endif %}
+ {% endfor -%}
+ ){% endif -%}
+ {% endfor %}
+
+ {% endif -%}
+ default: throw UniffiInternalError.unexpectedEnumCase
+ }
+ }
+
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ switch value {
+
+ {% if e.is_flat() %}
+
+ {% for variant in e.variants() %}
+ case let .{{ variant.name()|class_name }}(message):
+ writeInt(&buf, Int32({{ loop.index }}))
+ {{ Type::String.borrow()|write_fn }}(message, into: &buf)
+ {%- endfor %}
+
+ {% else %}
+
+ {% for variant in e.variants() %}
+ {% if variant.has_fields() %}
+ case let .{{ variant.name()|class_name }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- 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)
+ {% endfor -%}
+ {% else %}
+ case .{{ variant.name()|class_name }}:
+ writeInt(&buf, Int32({{ loop.index }}))
+ {% endif %}
+ {%- endfor %}
+
+ {%- endif %}
+ }
+ }
+}
+
+{% if !contains_object_references %}
+extension {{ type_name }}: Equatable, Hashable {}
+{% endif %}
+extension {{ type_name }}: Error { }
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift
new file mode 100644
index 0000000000..fb986beab6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterFloat: FfiConverterPrimitive {
+ typealias FfiType = Float
+ typealias SwiftType = Float
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float {
+ return try lift(readFloat(&buf))
+ }
+
+ public static func write(_ value: Float, into buf: inout [UInt8]) {
+ writeFloat(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift
new file mode 100644
index 0000000000..74421c045c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterDouble: FfiConverterPrimitive {
+ typealias FfiType = Double
+ typealias SwiftType = Double
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Double {
+ return try lift(readDouble(&buf))
+ }
+
+ public static func write(_ value: Double, into buf: inout [UInt8]) {
+ writeDouble(&buf, lower(value))
+ }
+}
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
new file mode 100644
index 0000000000..18e2397733
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
@@ -0,0 +1,84 @@
+// An error type for FFI errors. These errors occur at the UniFFI level, not
+// the library level.
+fileprivate enum UniffiInternalError: LocalizedError {
+ case bufferOverflow
+ case incompleteData
+ case unexpectedOptionalTag
+ case unexpectedEnumCase
+ case unexpectedNullPointer
+ case unexpectedRustCallStatusCode
+ case unexpectedRustCallError
+ case unexpectedStaleHandle
+ case rustPanic(_ message: String)
+
+ public var errorDescription: String? {
+ switch self {
+ case .bufferOverflow: return "Reading the requested value would read past the end of the buffer"
+ case .incompleteData: return "The buffer still has data after lifting its containing value"
+ case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1"
+ case .unexpectedEnumCase: return "Raw enum value doesn't match any cases"
+ case .unexpectedNullPointer: return "Raw pointer value was null"
+ case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code"
+ case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified"
+ case .unexpectedStaleHandle: return "The object in the handle map has been dropped already"
+ case let .rustPanic(message): return message
+ }
+ }
+}
+
+fileprivate let CALL_SUCCESS: Int8 = 0
+fileprivate let CALL_ERROR: Int8 = 1
+fileprivate let CALL_PANIC: Int8 = 2
+
+fileprivate extension RustCallStatus {
+ init() {
+ self.init(
+ code: CALL_SUCCESS,
+ errorBuf: RustBuffer.init(
+ capacity: 0,
+ len: 0,
+ data: nil
+ )
+ )
+ }
+}
+
+private func rustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T {
+ try makeRustCall(callback, errorHandler: {
+ $0.deallocate()
+ return UniffiInternalError.unexpectedRustCallError
+ })
+}
+
+private func rustCallWithError<T, F: FfiConverter>
+ (_ errorFfiConverter: F.Type, _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T
+ where F.SwiftType: Error, F.FfiType == RustBuffer
+ {
+ try makeRustCall(callback, errorHandler: { return try errorFfiConverter.lift($0) })
+}
+
+private func makeRustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T, errorHandler: (RustBuffer) throws -> Error) throws -> T {
+ var callStatus = RustCallStatus.init()
+ let returnedVal = callback(&callStatus)
+ switch callStatus.code {
+ case CALL_SUCCESS:
+ return returnedVal
+
+ case CALL_ERROR:
+ throw try errorHandler(callStatus.errorBuf)
+
+ case CALL_PANIC:
+ // 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.
+ if callStatus.errorBuf.len > 0 {
+ throw UniffiInternalError.rustPanic(try {{ Type::String.borrow()|lift_fn }}(callStatus.errorBuf))
+ } else {
+ callStatus.errorBuf.deallocate()
+ throw UniffiInternalError.rustPanic("Rust panic")
+ }
+
+ default:
+ throw UniffiInternalError.unexpectedRustCallStatusCode
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift
new file mode 100644
index 0000000000..ac57fc5e58
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterInt16: FfiConverterPrimitive {
+ typealias FfiType = Int16
+ typealias SwiftType = Int16
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int16 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Int16, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift
new file mode 100644
index 0000000000..0ccfc13e4e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterInt32: FfiConverterPrimitive {
+ typealias FfiType = Int32
+ typealias SwiftType = Int32
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Int32, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift
new file mode 100644
index 0000000000..d7d4082933
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterInt64: FfiConverterPrimitive {
+ typealias FfiType = Int64
+ typealias SwiftType = Int64
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Int64, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift
new file mode 100644
index 0000000000..f2387e4340
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterInt8: FfiConverterPrimitive {
+ typealias FfiType = Int8
+ typealias SwiftType = Int8
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int8 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: Int8, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift
new file mode 100644
index 0000000000..05713aca26
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift
@@ -0,0 +1,22 @@
+fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ let len = Int32(value.count)
+ writeInt(&buf, len)
+ for (key, value) in value {
+ {{ key_type|write_fn }}(key, into: &buf)
+ {{ value_type|write_fn }}(value, into: &buf)
+ }
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
+ let len: Int32 = try readInt(&buf)
+ var dict = {{ type_name }}()
+ dict.reserveCapacity(Int(len))
+ for _ in 0..<len {
+ let key = try {{ key_type|read_fn }}(from: &buf)
+ let value = try {{ value_type|read_fn }}(from: &buf)
+ dict[key] = value
+ }
+ return dict
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap
new file mode 100644
index 0000000000..f5f73ceb1b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap
@@ -0,0 +1,6 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+module {{ config.ffi_module_name() }} {
+ header "{{ config.header_filename() }}"
+ export *
+}
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
new file mode 100644
index 0000000000..ab0b22b1d8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
@@ -0,0 +1,88 @@
+{%- let obj = ci.get_object_definition(name).unwrap() %}
+public protocol {{ obj.name() }}Protocol {
+ {% for meth in obj.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 %}
+}
+
+public class {{ type_name }}: {{ obj.name() }}Protocol {
+ fileprivate let pointer: UnsafeMutableRawPointer
+
+ // 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) {
+ self.pointer = pointer
+ }
+
+ {%- 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) %})
+ }
+ {%- when None %}
+ {%- endmatch %}
+
+ deinit {
+ 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) %})
+ }
+ {% endfor %}
+
+ {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #}
+ {% for meth in obj.methods() -%}
+ {%- 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 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) %}
+ }
+ {%- endmatch %}
+ {% endfor %}
+}
+
+
+public struct {{ ffi_converter_name }}: FfiConverter {
+ typealias FfiType = UnsafeMutableRawPointer
+ typealias SwiftType = {{ type_name }}
+
+ 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.
+ // We have to go via `UInt` because that's the thing that's the size of a pointer.
+ let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v))
+ if (ptr == nil) {
+ throw UniffiInternalError.unexpectedNullPointer
+ }
+ return try lift(ptr!)
+ }
+
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ // This fiddling is because `Int` is the thing that's the same size as a pointer.
+ // 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)
+ }
+
+ public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer {
+ return value.pointer
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift
new file mode 100644
index 0000000000..1dac65be63
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift
@@ -0,0 +1,20 @@
+fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ typealias SwiftType = {{ type_name }}
+
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
+ guard let value = value else {
+ writeInt(&buf, Int8(0))
+ return
+ }
+ writeInt(&buf, Int8(1))
+ {{ inner_type|write_fn }}(value, into: &buf)
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
+ switch try readInt(&buf) as Int8 {
+ case 0: return nil
+ case 1: return try {{ inner_type|read_fn }}(from: &buf)
+ default: throw UniffiInternalError.unexpectedOptionalTag
+ }
+ }
+}
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
new file mode 100644
index 0000000000..7f2de44052
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
@@ -0,0 +1,62 @@
+{%- let rec = ci.get_record_definition(name).unwrap() %}
+public struct {{ type_name }} {
+ {%- for field in rec.fields() %}
+ public var {{ 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) %}) {
+ {%- for field in rec.fields() %}
+ self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
+ {%- endfor %}
+ }
+}
+
+{% if !contains_object_references %}
+extension {{ type_name }}: Equatable, Hashable {
+ public static func ==(lhs: {{ type_name }}, rhs: {{ type_name }}) -> Bool {
+ {%- for field in rec.fields() %}
+ if lhs.{{ field.name()|var_name }} != rhs.{{ field.name()|var_name }} {
+ return false
+ }
+ {%- endfor %}
+ return true
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ {%- for field in rec.fields() %}
+ hasher.combine({{ field.name()|var_name }})
+ {%- endfor %}
+ }
+}
+{% endif %}
+
+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 }}(
+ {%- for field in rec.fields() %}
+ {{ field.name()|var_name }}: {{ field|read_fn }}(from: &buf)
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}
+ )
+ }
+
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ {%- for field in rec.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, into: &buf)
+ {%- endfor %}
+ }
+}
+
+{#
+We always write these public functions just in case the struct is used as
+an external type by another crate.
+#}
+public func {{ ffi_converter_name }}_lift(_ buf: RustBuffer) throws -> {{ type_name }} {
+ return try {{ ffi_converter_name }}.lift(buf)
+}
+
+public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuffer {
+ return {{ ffi_converter_name }}.lower(value)
+}
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
new file mode 100644
index 0000000000..2f737b6635
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
@@ -0,0 +1,183 @@
+fileprivate extension RustBuffer {
+ // Allocate a new buffer, copying the contents of a `UInt8` array.
+ init(bytes: [UInt8]) {
+ let rbuf = bytes.withUnsafeBufferPointer { ptr in
+ RustBuffer.from(ptr)
+ }
+ self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data)
+ }
+
+ static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer {
+ try! rustCall { {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), $0) }
+ }
+
+ // Frees the buffer in place.
+ // The buffer must not be used after this is called.
+ func deallocate() {
+ try! rustCall { {{ ci.ffi_rustbuffer_free().name() }}(self, $0) }
+ }
+}
+
+fileprivate extension ForeignBytes {
+ init(bufferPointer: UnsafeBufferPointer<UInt8>) {
+ self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress)
+ }
+}
+
+// For every type used in the interface, we provide helper methods for conveniently
+// lifting and lowering that type from C-compatible data, and for reading and writing
+// values of that type in a buffer.
+
+// Helper classes/extensions that don't change.
+// Someday, this will be in a library of its own.
+
+fileprivate extension Data {
+ init(rustBuffer: RustBuffer) {
+ // TODO: This copies the buffer. Can we read directly from a
+ // Rust buffer?
+ self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len))
+ }
+}
+
+// Define reader functionality. Normally this would be defined in a class or
+// struct, but we use standalone functions instead in order to make external
+// types work.
+//
+// With external types, one swift source file needs to be able to call the read
+// method on another source file's FfiConverter, but then what visibility
+// should Reader have?
+// - If Reader is fileprivate, then this means the read() must also
+// be fileprivate, which doesn't work with external types.
+// - If Reader is internal/public, we'll get compile errors since both source
+// files will try define the same type.
+//
+// Instead, the read() method and these helper functions input a tuple of data
+
+fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) {
+ (data: data, offset: 0)
+}
+
+// Reads an integer at the current offset, in big-endian order, and advances
+// the offset on success. Throws if reading the integer would move the
+// offset past the end of the buffer.
+fileprivate func readInt<T: FixedWidthInteger>(_ reader: inout (data: Data, offset: Data.Index)) throws -> T {
+ let range = reader.offset..<reader.offset + MemoryLayout<T>.size
+ guard reader.data.count >= range.upperBound else {
+ throw UniffiInternalError.bufferOverflow
+ }
+ if T.self == UInt8.self {
+ let value = reader.data[reader.offset]
+ reader.offset += 1
+ return value as! T
+ }
+ var value: T = 0
+ let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)})
+ reader.offset = range.upperBound
+ return value.bigEndian
+}
+
+// Reads an arbitrary number of bytes, to be used to read
+// raw bytes, this is useful when lifting strings
+fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array<UInt8> {
+ let range = reader.offset..<(reader.offset+count)
+ guard reader.data.count >= range.upperBound else {
+ throw UniffiInternalError.bufferOverflow
+ }
+ var value = [UInt8](repeating: 0, count: count)
+ value.withUnsafeMutableBufferPointer({ buffer in
+ reader.data.copyBytes(to: buffer, from: range)
+ })
+ reader.offset = range.upperBound
+ return value
+}
+
+// Reads a float at the current offset.
+fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float {
+ return Float(bitPattern: try readInt(&reader))
+}
+
+// Reads a float at the current offset.
+fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double {
+ return Double(bitPattern: try readInt(&reader))
+}
+
+// Indicates if the offset has reached the end of the buffer.
+fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool {
+ return reader.offset < reader.data.count
+}
+
+// Define writer functionality. Normally this would be defined in a class or
+// struct, but we use standalone functions instead in order to make external
+// types work. See the above discussion on Readers for details.
+
+fileprivate func createWriter() -> [UInt8] {
+ return []
+}
+
+fileprivate func writeBytes<S>(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 {
+ writer.append(contentsOf: byteArr)
+}
+
+// Writes an integer in big-endian order.
+//
+// Warning: make sure what you are trying to write
+// is in the correct type!
+fileprivate func writeInt<T: FixedWidthInteger>(_ writer: inout [UInt8], _ value: T) {
+ var value = value.bigEndian
+ withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) }
+}
+
+fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) {
+ writeInt(&writer, value.bitPattern)
+}
+
+fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) {
+ writeInt(&writer, value.bitPattern)
+}
+
+// Protocol for types that transfer other types across the FFI. This is
+// analogous go the Rust trait of the same name.
+fileprivate protocol FfiConverter {
+ associatedtype FfiType
+ associatedtype SwiftType
+
+ static func lift(_ value: FfiType) throws -> SwiftType
+ static func lower(_ value: SwiftType) -> FfiType
+ static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType
+ static func write(_ value: SwiftType, into buf: inout [UInt8])
+}
+
+// Types conforming to `Primitive` pass themselves directly over the FFI.
+fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { }
+
+extension FfiConverterPrimitive {
+ public static func lift(_ value: FfiType) throws -> SwiftType {
+ return value
+ }
+
+ public static func lower(_ value: SwiftType) -> FfiType {
+ return value
+ }
+}
+
+// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`.
+// Used for complex types where it's hard to write a custom lift/lower.
+fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {}
+
+extension FfiConverterRustBuffer {
+ public static func lift(_ buf: RustBuffer) throws -> SwiftType {
+ var reader = createReader(data: Data(rustBuffer: buf))
+ let value = try read(from: &reader)
+ if hasRemaining(reader) {
+ throw UniffiInternalError.incompleteData
+ }
+ buf.deallocate()
+ return value
+ }
+
+ public static func lower(_ value: SwiftType) -> RustBuffer {
+ var writer = createWriter()
+ write(value, into: &writer)
+ return RustBuffer(bytes: writer)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift
new file mode 100644
index 0000000000..bf664f6411
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift
@@ -0,0 +1,21 @@
+fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
+ typealias SwiftType = {{ type_name }}
+
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ let len = Int32(value.count)
+ writeInt(&buf, len)
+ for item in value {
+ {{ inner_type|write_fn }}(item, into: &buf)
+ }
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
+ let len: Int32 = try readInt(&buf)
+ var seq = {{ type_name }}()
+ seq.reserveCapacity(Int(len))
+ for _ in 0 ..< len {
+ seq.append(try {{ inner_type|read_fn }}(from: &buf))
+ }
+ return seq
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift
new file mode 100644
index 0000000000..b7d3466bdd
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift
@@ -0,0 +1,37 @@
+fileprivate struct FfiConverterString: FfiConverter {
+ typealias SwiftType = String
+ typealias FfiType = RustBuffer
+
+ public static func lift(_ value: RustBuffer) throws -> String {
+ defer {
+ value.deallocate()
+ }
+ if value.data == nil {
+ return String()
+ }
+ let bytes = UnsafeBufferPointer<UInt8>(start: value.data!, count: Int(value.len))
+ return String(bytes: bytes, encoding: String.Encoding.utf8)!
+ }
+
+ public static func lower(_ value: String) -> RustBuffer {
+ return value.utf8CString.withUnsafeBufferPointer { ptr in
+ // The swift string gives us int8_t, we want uint8_t.
+ ptr.withMemoryRebound(to: UInt8.self) { ptr in
+ // The swift string gives us a trailing null byte, we don't want it.
+ let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1))
+ return RustBuffer.from(buf)
+ }
+ }
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String {
+ let len: Int32 = try readInt(&buf)
+ return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)!
+ }
+
+ public static func write(_ value: String, into buf: inout [UInt8]) {
+ let len = Int32(value.utf8.count)
+ writeInt(&buf, len)
+ writeBytes(&buf, value.utf8)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift
new file mode 100644
index 0000000000..3cd472fa0e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift
@@ -0,0 +1,34 @@
+fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer {
+ typealias SwiftType = Date
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Date {
+ let seconds: Int64 = try readInt(&buf)
+ let nanoseconds: UInt32 = try readInt(&buf)
+ if seconds >= 0 {
+ let delta = Double(seconds) + (Double(nanoseconds) / 1.0e9)
+ return Date.init(timeIntervalSince1970: delta)
+ } else {
+ let delta = Double(seconds) - (Double(nanoseconds) / 1.0e9)
+ return Date.init(timeIntervalSince1970: delta)
+ }
+ }
+
+ public static func write(_ value: Date, into buf: inout [UInt8]) {
+ var delta = value.timeIntervalSince1970
+ var sign: Int64 = 1
+ if delta < 0 {
+ // The nanoseconds portion of the epoch offset must always be
+ // positive, to simplify the calculation we will use the absolute
+ // value of the offset.
+ sign = -1
+ delta = -delta
+ }
+ if delta.rounded(.down) > Double(Int64.max) {
+ fatalError("Timestamp overflow, exceeds max bounds supported by Uniffi")
+ }
+ let seconds = Int64(delta)
+ let nanoseconds = UInt32((delta - Double(seconds)) * 1.0e9)
+ writeInt(&buf, sign * seconds)
+ writeInt(&buf, nanoseconds)
+ }
+}
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
new file mode 100644
index 0000000000..5ce762d2a0
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
@@ -0,0 +1,15 @@
+{%- 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 %}
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
new file mode 100644
index 0000000000..6e37e1b7f3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
@@ -0,0 +1,90 @@
+{%- import "macros.swift" as swift %}
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+{%- let contains_object_references = ci.item_contains_object_references(type_) %}
+
+{#
+ # Map `Type` instances to an include statement for that type.
+ #
+ # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the
+ # Rust code.
+ #
+ # - When adding additional types here, make sure to also add a match arm to that function.
+ # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ #}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "BooleanHelper.swift" %}
+
+{%- when Type::String %}
+{%- include "StringHelper.swift" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8Helper.swift" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16Helper.swift" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32Helper.swift" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64Helper.swift" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8Helper.swift" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16Helper.swift" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32Helper.swift" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64Helper.swift" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32Helper.swift" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64Helper.swift" %}
+
+{%- when Type::Timestamp %}
+{%- include "TimestampHelper.swift" %}
+
+{%- when Type::Duration %}
+{%- include "DurationHelper.swift" %}
+
+{%- when Type::CallbackInterface(name) %}
+{%- include "CallbackInterfaceTemplate.swift" %}
+
+{%- when Type::Custom { name, builtin } %}
+{%- include "CustomType.swift" %}
+
+{%- when Type::Enum(name) %}
+{%- include "EnumTemplate.swift" %}
+
+{%- when Type::Error(name) %}
+{%- include "ErrorTemplate.swift" %}
+
+{%- when Type::Object(name) %}
+{%- include "ObjectTemplate.swift" %}
+
+{%- when Type::Record(name) %}
+{%- include "RecordTemplate.swift" %}
+
+{%- when Type::Optional(inner_type) %}
+{%- include "OptionalTemplate.swift" %}
+
+{%- when Type::Sequence(inner_type) %}
+{%- include "SequenceTemplate.swift" %}
+
+{%- when Type::Map(key_type, value_type) %}
+{%- include "MapTemplate.swift" %}
+
+{%- else %}
+{%- endmatch %}
+{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift
new file mode 100644
index 0000000000..b7fc0942a5
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterUInt16: FfiConverterPrimitive {
+ typealias FfiType = UInt16
+ typealias SwiftType = UInt16
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt16 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift
new file mode 100644
index 0000000000..e7a64aab93
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterUInt32: FfiConverterPrimitive {
+ typealias FfiType = UInt32
+ typealias SwiftType = UInt32
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift
new file mode 100644
index 0000000000..eb674a2c53
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterUInt64: FfiConverterPrimitive {
+ typealias FfiType = UInt64
+ typealias SwiftType = UInt64
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt64 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift
new file mode 100644
index 0000000000..4baf613494
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift
@@ -0,0 +1,12 @@
+fileprivate struct FfiConverterUInt8: FfiConverterPrimitive {
+ typealias FfiType = UInt8
+ typealias SwiftType = UInt8
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 {
+ return try lift(readInt(&buf))
+ }
+
+ public static func write(_ value: UInt8, into buf: inout [UInt8]) {
+ writeInt(&buf, lower(value))
+ }
+}
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
new file mode 100644
index 0000000000..dcc0a2df74
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
@@ -0,0 +1,97 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call`
+#}
+
+{%- macro to_ffi_call(func) -%}
+{% call try(func) %}
+ {% match func.throws_type() %}
+ {% when Some with (e) %}
+ rustCallWithError({{ e|ffi_converter_name }}.self) {
+ {% else %}
+ rustCall() {
+ {% endmatch %}
+ {{ func.ffi_func().name() }}({% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %}, {% endif %}$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 }}.self) {
+ {%- else %}
+ rustCall() {
+ {% endmatch %}
+ {{ func.ffi_func().name() }}(
+ {{- prefix }}, {% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %}, {% endif %}$0
+ )
+}
+{%- endmacro %}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg|lower_fn }}({{ arg.name()|var_name }})
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in Swift declarations of methods, functions and constructors.
+// Note the var_name and type_name filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {% if config.omit_argument_labels() %}_ {% endif %}{{ arg.name()|var_name }}: {{ arg|type_name -}}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|literal_swift(arg) }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{#-
+// Field lists as used in Swift declarations of Records and Enums.
+// Note the var_name and type_name filters.
+-#}
+{% macro field_list_decl(item) %}
+ {%- for field in item.fields() -%}
+ {{ 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 %}
+ {%- endfor %}
+{%- endmacro %}
+
+
+{% macro arg_list_protocol(func) %}
+ {%- for arg in func.arguments() -%}
+ {% if config.omit_argument_labels() %}_ {% endif %}{{ arg.name()|var_name }}: {{ arg|type_name -}}
+ {%- if !loop.last %}, {% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+
+{#-
+// 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.type_().borrow()|ffi_type_name }} {{ arg.name() -}},
+ {%- endfor %}
+ RustCallStatus *_Nonnull out_status
+{%- endmacro -%}
+
+{%- macro throws(func) %}
+{%- if func.throws() %}throws{% endif %}
+{%- endmacro -%}
+
+{%- macro try(func) %}
+{%- if func.throws() %}try{% else %}try!{% endif %}
+{%- 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
new file mode 100644
index 0000000000..ac0557912e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
@@ -0,0 +1,40 @@
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+{%- import "macros.swift" as swift %}
+import Foundation
+{%- for imported_class in self.imports() %}
+import {{ imported_class }}
+{%- endfor %}
+
+// Depending on the consumer's build setup, the low-level FFI code
+// might be in a separate module, or it might be compiled inline into
+// this module. This is a bit of light hackery to work with both.
+#if canImport({{ config.ffi_module_name() }})
+import {{ config.ffi_module_name() }}
+#endif
+
+{% include "RustBufferTemplate.swift" %}
+{% include "Helpers.swift" %}
+
+// Public interface members begin here.
+{{ type_helper_code }}
+
+{%- for func in ci.function_definitions() %}
+{%- include "TopLevelFunctionTemplate.swift" %}
+{%- endfor %}
+
+/**
+ * Top level initializers and tear down methods.
+ *
+ * This is generated by uniffi.
+ */
+public enum {{ config.module_name().borrow()|class_name }}Lifecycle {
+ /**
+ * Initialize the FFI and Rust library. This should be only called once per application.
+ */
+ func initialize() {
+ {%- for initialization_fn in self.initialization_fns() %}
+ {{ initialization_fn }}()
+ {%- endfor %}
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
new file mode 100644
index 0000000000..e51c4a9c05
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
@@ -0,0 +1,225 @@
+/* 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 anyhow::{bail, Context, Result};
+use camino::{Utf8Path, Utf8PathBuf};
+use heck::ToSnakeCase;
+use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
+use std::ffi::OsStr;
+use std::fs::{read_to_string, File};
+use std::io::Write;
+use std::process::Command;
+use uniffi_testing::{CompileSource, UniFFITestHelper};
+
+/// Run Swift tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir()")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_fixture_library_to_out_dir()")?;
+ let generated_sources =
+ GeneratedSources::new(&test_helper.cdylib_path()?, &out_dir, &test_helper)
+ .context("generate_sources()")?;
+
+ // Compile the generated sources together to create a single swift module
+ compile_swift_module(
+ &out_dir,
+ &calc_module_name(&generated_sources.main_source_filename),
+ &generated_sources.generated_swift_files,
+ &generated_sources.module_map,
+ )?;
+
+ // Run the test script against compiled bindings
+
+ let mut command = Command::new("swift");
+ command
+ .current_dir(&out_dir)
+ .arg("-I")
+ .arg(&out_dir)
+ .arg("-L")
+ .arg(&out_dir)
+ .args(calc_library_args(&out_dir)?)
+ .arg("-Xcc")
+ .arg(format!(
+ "-fmodule-map-file={}",
+ generated_sources.module_map
+ ))
+ .arg(&script_path);
+ let status = command
+ .spawn()
+ .context("Failed to spawn `swiftc` when running test script")?
+ .wait()
+ .context("Failed to wait for `swiftc` when running test script")?;
+ if !status.success() {
+ bail!("running `swift` to run test script failed ({:?})", command)
+ }
+ Ok(())
+}
+
+fn calc_module_name(filename: &str) -> String {
+ filename.strip_suffix(".swift").unwrap().to_snake_case()
+}
+
+fn compile_swift_module<T: AsRef<OsStr>>(
+ out_dir: &Utf8Path,
+ module_name: &str,
+ sources: impl IntoIterator<Item = T>,
+ module_map: &Utf8Path,
+) -> Result<()> {
+ let output_filename = format!("{DLL_PREFIX}testmod_{module_name}{DLL_SUFFIX}");
+ let mut command = Command::new("swiftc");
+ command
+ .current_dir(out_dir)
+ .arg("-emit-module")
+ .arg("-module-name")
+ .arg(module_name)
+ .arg("-o")
+ .arg(output_filename)
+ .arg("-emit-library")
+ .arg("-Xcc")
+ .arg(format!("-fmodule-map-file={}", module_map))
+ .arg("-I")
+ .arg(out_dir)
+ .arg("-L")
+ .arg(out_dir)
+ .args(calc_library_args(out_dir)?)
+ .args(sources);
+ let status = command
+ .spawn()
+ .context("Failed to spawn `swiftc` when compiling bindings")?
+ .wait()
+ .context("Failed to wait for `swiftc` when compiling bindings")?;
+ if !status.success() {
+ bail!(
+ "running `swiftc` to compile bindings failed ({:?})",
+ command
+ )
+ };
+ Ok(())
+}
+
+// Stores sources generated by `uniffi-bindgen-swift`
+struct GeneratedSources {
+ generated_swift_files: Vec<Utf8PathBuf>,
+ module_map: Utf8PathBuf,
+ main_source_filename: String,
+}
+
+impl GeneratedSources {
+ fn new(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+ ) -> Result<Self> {
+ // Generate the bindings for the main compile source, and use that for the swift module name
+ let main_compile_source = test_helper.get_main_compile_source()?;
+ Self::run_generate_bindings(&main_compile_source, library_path, out_dir)?;
+ let generated_files = glob(&out_dir.join("*.swift"))?;
+ let main_source_filename = match generated_files.len() {
+ 0 => bail!(
+ "No .swift file generated for {}",
+ main_compile_source.udl_path
+ ),
+ 1 => generated_files
+ .into_iter()
+ .next()
+ .unwrap()
+ .file_name()
+ .unwrap()
+ .to_string(),
+ n => bail!(
+ "{n} .swift files generated for {}",
+ main_compile_source.udl_path
+ ),
+ };
+
+ // Generate the bindings for other compile sources (crates used by external types)
+ for source in test_helper.get_external_compile_sources()? {
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["swift"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+
+ let generated_module_maps = glob(&out_dir.join("*.modulemap"))?;
+
+ Ok(GeneratedSources {
+ main_source_filename,
+ generated_swift_files: glob(&out_dir.join("*.swift"))?,
+ module_map: match generated_module_maps.len() {
+ 0 => bail!("No modulemap files found in {out_dir}"),
+ // Normally we only generate 1 module map and can return it directly
+ 1 => generated_module_maps.into_iter().next().unwrap(),
+ // When we use multiple UDL files in a test, for example the ext-types fixture,
+ // then we get multiple module maps and need to combine them
+ _ => {
+ let path = out_dir.join("combined.modulemap");
+ let mut f = File::create(&path)?;
+ write!(
+ f,
+ "{}",
+ generated_module_maps
+ .into_iter()
+ .map(|path| Ok(read_to_string(path)?))
+ .collect::<Result<Vec<String>>>()?
+ .join("\n")
+ )?;
+ path
+ }
+ },
+ })
+ }
+
+ fn run_generate_bindings(
+ source: &CompileSource,
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ ) -> Result<()> {
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["swift"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )
+ }
+}
+
+// Wraps glob to use Utf8Paths and flattens errors
+fn glob(globspec: &Utf8Path) -> Result<Vec<Utf8PathBuf>> {
+ glob::glob(globspec.as_str())?
+ .map(|globresult| Ok(Utf8PathBuf::try_from(globresult?)?))
+ .collect()
+}
+
+fn calc_library_args(out_dir: &Utf8Path) -> Result<Vec<String>> {
+ let results = glob::glob(
+ out_dir
+ .join(format!("{}*{}", DLL_PREFIX, DLL_SUFFIX))
+ .as_str(),
+ )?;
+ results
+ .map(|globresult| {
+ let path = Utf8PathBuf::try_from(globresult.unwrap())?;
+ Ok(format!(
+ "-l{}",
+ path.file_name()
+ .unwrap()
+ .strip_prefix(DLL_PREFIX)
+ .unwrap()
+ .strip_suffix(DLL_SUFFIX)
+ .unwrap()
+ ))
+ })
+ .collect()
+}