summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/bindings/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings/kotlin')
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs31
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs98
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs30
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs530
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs86
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs41
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt44
-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/ByteArrayHelper.kt15
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt78
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt129
-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.kt110
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt111
-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/ForeignExecutorTemplate.kt83
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt161
-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.kt34
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt57
-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.kt138
-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.kt42
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt87
-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.kt53
-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.kt51
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt109
-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.kt77
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt57
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs131
51 files changed, 3224 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..e20020e87c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CodeType;
+use crate::ComponentInterface;
+
+#[derive(Debug)]
+pub struct CallbackInterfaceCodeType {
+ id: String,
+}
+
+impl CallbackInterfaceCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for CallbackInterfaceCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ super::KotlinCodeOracle.class_name(ci, &self.id)
+ }
+
+ fn canonical_name(&self) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn initialization_fn(&self) -> Option<String> {
+ Some(format!("{}.register", self.ffi_converter_name()))
+ }
+}
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..4329f32f4c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/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 super::{AsCodeType, CodeType};
+use crate::backend::{Literal, Type};
+use crate::ComponentInterface;
+use paste::paste;
+
+fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::Null => "null".into(),
+ Literal::EmptySequence => "listOf()".into(),
+ Literal::EmptyMap => "mapOf()".into(),
+
+ // For optionals
+ _ => super::KotlinCodeOracle.find(inner).literal(literal, ci),
+ }
+}
+
+macro_rules! impl_code_type_for_compound {
+ ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => {
+ paste! {
+ #[derive(Debug)]
+ pub struct $T {
+ inner: Type,
+ }
+
+ impl $T {
+ pub fn new(inner: Type) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &Type {
+ &self.inner
+ }
+ }
+
+ impl CodeType for $T {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label(ci))
+ }
+
+ fn canonical_name(&self) -> String {
+ format!($canonical_name_pattern, super::KotlinCodeOracle.find(self.inner()).canonical_name())
+ }
+
+ fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
+ render_literal(literal, self.inner(), ci)
+ }
+ }
+ }
+ }
+ }
+
+impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}");
+impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}");
+
+#[derive(Debug)]
+pub struct MapCodeType {
+ key: Type,
+ value: Type,
+}
+
+impl MapCodeType {
+ pub fn new(key: Type, value: Type) -> Self {
+ Self { key, value }
+ }
+
+ fn key(&self) -> &Type {
+ &self.key
+ }
+
+ fn value(&self) -> &Type {
+ &self.value
+ }
+}
+
+impl CodeType for MapCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ format!(
+ "Map<{}, {}>",
+ super::KotlinCodeOracle.find(self.key()).type_label(ci),
+ super::KotlinCodeOracle.find(self.value()).type_label(ci),
+ )
+ }
+
+ fn canonical_name(&self) -> String {
+ format!(
+ "Map{}{}",
+ self.key().as_codetype().canonical_name(),
+ self.value().as_codetype().canonical_name(),
+ )
+ }
+
+ fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
+ render_literal(literal, &self.value, ci)
+ }
+}
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..137cd0d8d9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CodeType;
+use crate::ComponentInterface;
+
+#[derive(Debug)]
+pub struct CustomCodeType {
+ name: String,
+}
+
+impl CustomCodeType {
+ pub fn new(name: String) -> Self {
+ CustomCodeType { name }
+ }
+}
+
+impl CodeType for CustomCodeType {
+ fn type_label(&self, _ci: &ComponentInterface) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self) -> String {
+ format!("Type{}", self.name)
+ }
+}
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..f5300c10ee
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.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 super::CodeType;
+use crate::backend::Literal;
+use crate::ComponentInterface;
+
+#[derive(Debug)]
+pub struct EnumCodeType {
+ id: String,
+}
+
+impl EnumCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for EnumCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ super::KotlinCodeOracle.class_name(ci, &self.id)
+ }
+
+ fn canonical_name(&self) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
+ if let Literal::Enum(v, _) = literal {
+ format!(
+ "{}.{}",
+ self.type_label(ci),
+ super::KotlinCodeOracle.enum_variant_name(v)
+ )
+ } else {
+ unreachable!();
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs
new file mode 100644
index 0000000000..154e12a381
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CodeType;
+use crate::ComponentInterface;
+
+#[derive(Debug)]
+pub struct ForeignExecutorCodeType;
+
+impl CodeType for ForeignExecutorCodeType {
+ fn type_label(&self, _ci: &ComponentInterface) -> String {
+ // Kotlin uses a CoroutineScope for ForeignExecutor
+ "CoroutineScope".into()
+ }
+
+ fn canonical_name(&self) -> String {
+ "ForeignExecutor".into()
+ }
+
+ fn initialization_fn(&self) -> Option<String> {
+ Some("FfiConverterForeignExecutor.register".into())
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
new file mode 100644
index 0000000000..3ecf09d47f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CodeType;
+use crate::ComponentInterface;
+
+#[derive(Debug)]
+pub struct ExternalCodeType {
+ name: String,
+}
+
+impl ExternalCodeType {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+impl CodeType for ExternalCodeType {
+ fn type_label(&self, _ci: &ComponentInterface) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self) -> String {
+ format!("Type{}", self.name)
+ }
+}
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..17331ff511
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CodeType;
+use crate::ComponentInterface;
+use paste::paste;
+
+macro_rules! impl_code_type_for_miscellany {
+ ($T:ty, $class_name:literal, $canonical_name:literal) => {
+ paste! {
+ #[derive(Debug)]
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _ci: &ComponentInterface) -> String {
+ $class_name.into()
+ }
+
+ fn canonical_name(&self) -> String {
+ $canonical_name.into()
+ }
+ }
+ }
+ };
+}
+
+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..1ed0575a9a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
@@ -0,0 +1,530 @@
+/* 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 std::fmt::Debug;
+
+use anyhow::{Context, Result};
+use askama::Template;
+use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
+use serde::{Deserialize, Serialize};
+
+use crate::backend::TemplateExpression;
+use crate::interface::*;
+use crate::BindingsConfig;
+
+mod callback_interface;
+mod compounds;
+mod custom;
+mod enum_;
+mod executor;
+mod external;
+mod miscellany;
+mod object;
+mod primitives;
+mod record;
+mod variant;
+
+trait CodeType: Debug {
+ /// The language specific label used to reference this type. This will be used in
+ /// method signatures and property declarations.
+ fn type_label(&self, ci: &ComponentInterface) -> String;
+
+ /// A representation of this type label that can be used as part of another
+ /// identifier. e.g. `read_foo()`, or `FooInternals`.
+ ///
+ /// This is especially useful when creating specialized objects or methods to deal
+ /// with this type only.
+ fn canonical_name(&self) -> String;
+
+ fn literal(&self, _literal: &Literal, ci: &ComponentInterface) -> String {
+ unimplemented!("Unimplemented for {}", self.type_label(ci))
+ }
+
+ /// Name of the FfiConverter
+ ///
+ /// This is the object that contains the lower, write, lift, and read methods for this type.
+ /// Depending on the binding this will either be a singleton or a class with static methods.
+ ///
+ /// This is the newer way of handling these methods and replaces the lower, write, lift, and
+ /// read CodeType methods. Currently only used by Kotlin, but the plan is to move other
+ /// backends to using this.
+ fn ffi_converter_name(&self) -> String {
+ format!("FfiConverter{}", self.canonical_name())
+ }
+
+ /// A list of imports that are needed if this type is in use.
+ /// Classes are imported exactly once.
+ fn imports(&self) -> Option<Vec<String>> {
+ None
+ }
+
+ /// Function to run at startup
+ fn initialization_fn(&self) -> Option<String> {
+ None
+ }
+}
+
+// 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 BindingsConfig for Config {
+ fn update_from_ci(&mut self, ci: &ComponentInterface) {
+ self.package_name
+ .get_or_insert_with(|| format!("uniffi.{}", ci.namespace()));
+ self.cdylib_name
+ .get_or_insert_with(|| format!("uniffi_{}", ci.namespace()));
+ }
+
+ fn update_from_cdylib_name(&mut self, cdylib_name: &str) {
+ self.cdylib_name
+ .get_or_insert_with(|| cdylib_name.to_string());
+ }
+
+ fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>) {
+ for (crate_name, config) in config_map {
+ if !self.external_packages.contains_key(crate_name) {
+ self.external_packages
+ .insert(crate_name.to_string(), config.package_name());
+ }
+ }
+ }
+}
+
+// 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> {
+ 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(config: &'a Config, ci: &'a ComponentInterface) -> Self {
+ Self {
+ 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, module_path: &str, namespace: &str) -> String {
+ // config overrides are keyed by the crate name, default fallback is the namespace.
+ let crate_name = module_path.split("::").next().unwrap();
+ match self.config.external_packages.get(crate_name) {
+ Some(name) => name.clone(),
+ // unreachable in library mode - all deps are in our config with correct namespace.
+ None => format!("uniffi.{namespace}"),
+ }
+ }
+
+ // 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>,
+ has_async_fns: bool,
+}
+
+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,
+ has_async_fns: ci.has_async_fns(),
+ }
+ }
+
+ pub fn initialization_fns(&self) -> Vec<String> {
+ self.ci
+ .iter_types()
+ .map(|t| KotlinCodeOracle.find(t))
+ .filter_map(|ct| ct.initialization_fn())
+ .chain(
+ self.has_async_fns
+ .then(|| "uniffiRustFutureContinuationCallback.register".into()),
+ )
+ .collect()
+ }
+
+ pub fn imports(&self) -> Vec<ImportRequirement> {
+ self.type_imports.iter().cloned().collect()
+ }
+}
+
+#[derive(Clone)]
+pub struct KotlinCodeOracle;
+
+impl KotlinCodeOracle {
+ fn find(&self, type_: &Type) -> Box<dyn CodeType> {
+ type_.clone().as_type().as_codetype()
+ }
+
+ /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, ci: &ComponentInterface, nm: &str) -> String {
+ let name = nm.to_string().to_upper_camel_case();
+ // fixup errors.
+ ci.is_name_used_as_error(nm)
+ .then(|| self.convert_error_suffix(&name))
+ .unwrap_or(name)
+ }
+
+ fn convert_error_suffix(&self, nm: &str) -> String {
+ match nm.strip_suffix("Error") {
+ None => nm.to_string(),
+ Some(stripped) => format!("{stripped}Exception"),
+ }
+ }
+
+ /// 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()
+ }
+
+ fn ffi_type_label_by_value(ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)),
+ _ => Self::ffi_type_label(ffi_type),
+ }
+ }
+
+ fn ffi_type_label(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) => {
+ format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
+ }
+ FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
+ FfiType::ForeignCallback => "ForeignCallback".to_string(),
+ FfiType::ForeignExecutorHandle => "USize".to_string(),
+ FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(),
+ FfiType::RustFutureHandle => "Pointer".to_string(),
+ FfiType::RustFutureContinuationCallback => {
+ "UniFffiRustFutureContinuationCallbackType".to_string()
+ }
+ FfiType::RustFutureContinuationData => "USize".to_string(),
+ }
+ }
+}
+
+trait AsCodeType {
+ fn as_codetype(&self) -> Box<dyn CodeType>;
+}
+
+impl<T: AsType> AsCodeType for T {
+ fn as_codetype(&self) -> Box<dyn CodeType> {
+ // 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
+ match self.as_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::Bytes => Box::new(primitives::BytesCodeType),
+
+ Type::Timestamp => Box::new(miscellany::TimestampCodeType),
+ Type::Duration => Box::new(miscellany::DurationCodeType),
+
+ Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
+ Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
+ Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
+ Type::CallbackInterface { name, .. } => {
+ Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
+ }
+ Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
+ Type::Optional { inner_type } => {
+ Box::new(compounds::OptionalCodeType::new(*inner_type))
+ }
+ Type::Sequence { inner_type } => {
+ Box::new(compounds::SequenceCodeType::new(*inner_type))
+ }
+ Type::Map {
+ key_type,
+ value_type,
+ } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)),
+ Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
+ Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
+ }
+ }
+}
+
+mod filters {
+ use super::*;
+ pub use crate::backend::filters::*;
+
+ pub(super) fn type_name(
+ as_ct: &impl AsCodeType,
+ ci: &ComponentInterface,
+ ) -> Result<String, askama::Error> {
+ Ok(as_ct.as_codetype().type_label(ci))
+ }
+
+ pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(as_ct.as_codetype().canonical_name())
+ }
+
+ pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(as_ct.as_codetype().ffi_converter_name())
+ }
+
+ pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(format!(
+ "{}.lower",
+ as_ct.as_codetype().ffi_converter_name()
+ ))
+ }
+
+ pub(super) fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(format!(
+ "{}.allocationSize",
+ as_ct.as_codetype().ffi_converter_name()
+ ))
+ }
+
+ pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(format!(
+ "{}.write",
+ as_ct.as_codetype().ffi_converter_name()
+ ))
+ }
+
+ pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name()))
+ }
+
+ pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name()))
+ }
+
+ pub fn render_literal(
+ literal: &Literal,
+ as_ct: &impl AsType,
+ ci: &ComponentInterface,
+ ) -> Result<String, askama::Error> {
+ Ok(as_ct.as_codetype().literal(literal, ci))
+ }
+
+ pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle::ffi_type_label_by_value(type_))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a function name.
+ pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.fn_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a variable name.
+ pub fn var_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.var_name(nm))
+ }
+
+ /// Get a String representing the name used for an individual enum variant.
+ pub fn variant_name(v: &Variant) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.enum_variant_name(v.name()))
+ }
+
+ pub fn error_variant_name(v: &Variant) -> Result<String, askama::Error> {
+ let name = v.name().to_string().to_upper_camel_case();
+ Ok(KotlinCodeOracle.convert_error_suffix(&name))
+ }
+
+ pub fn async_poll(
+ callable: impl Callable,
+ ci: &ComponentInterface,
+ ) -> Result<String, askama::Error> {
+ let ffi_func = callable.ffi_rust_future_poll(ci);
+ Ok(format!(
+ "{{ future, continuation -> _UniFFILib.INSTANCE.{ffi_func}(future, continuation) }}"
+ ))
+ }
+
+ pub fn async_complete(
+ callable: impl Callable,
+ ci: &ComponentInterface,
+ ) -> Result<String, askama::Error> {
+ let ffi_func = callable.ffi_rust_future_complete(ci);
+ let call = format!("_UniFFILib.INSTANCE.{ffi_func}(future, continuation)");
+ let call = match callable.return_type() {
+ Some(Type::External {
+ kind: ExternalKind::DataClass,
+ name,
+ ..
+ }) => {
+ // Need to convert the RustBuffer from our package to the RustBuffer of the external package
+ let suffix = KotlinCodeOracle.class_name(ci, &name);
+ format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}")
+ }
+ _ => call,
+ };
+ Ok(format!("{{ future, continuation -> {call} }}"))
+ }
+
+ pub fn async_free(
+ callable: impl Callable,
+ ci: &ComponentInterface,
+ ) -> Result<String, askama::Error> {
+ let ffi_func = callable.ffi_rust_future_free(ci);
+ Ok(format!(
+ "{{ future -> _UniFFILib.INSTANCE.{ffi_func}(future) }}"
+ ))
+ }
+
+ /// 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..c39ae59cce
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CodeType;
+use crate::ComponentInterface;
+
+#[derive(Debug)]
+pub struct ObjectCodeType {
+ id: String,
+}
+
+impl ObjectCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ObjectCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ super::KotlinCodeOracle.class_name(ci, &self.id)
+ }
+
+ fn canonical_name(&self) -> String {
+ format!("Type{}", self.id)
+ }
+}
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..22495fa209
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CodeType;
+use crate::backend::Literal;
+use crate::interface::{ComponentInterface, Radix, Type};
+use paste::paste;
+
+fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> 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! {
+ #[derive(Debug)]
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _ci: &ComponentInterface) -> String {
+ $class_name.into()
+ }
+
+ fn canonical_name(&self) -> String {
+ $class_name.into()
+ }
+
+ fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
+ render_literal(&literal, ci)
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_primitive!(BooleanCodeType, "Boolean");
+impl_code_type_for_primitive!(StringCodeType, "String");
+impl_code_type_for_primitive!(BytesCodeType, "ByteArray");
+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..17781c2220
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CodeType;
+use crate::ComponentInterface;
+
+#[derive(Debug)]
+pub struct RecordCodeType {
+ id: String,
+}
+
+impl RecordCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for RecordCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ super::KotlinCodeOracle.class_name(ci, &self.id)
+ }
+
+ fn canonical_name(&self) -> String {
+ format!("Type{}", self.id)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs
new file mode 100644
index 0000000000..c7673882d9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.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 super::{AsCodeType, CodeType, KotlinCodeOracle};
+use crate::interface::{ComponentInterface, Variant};
+
+#[derive(Debug)]
+pub(super) struct VariantCodeType {
+ pub v: Variant,
+}
+
+impl CodeType for VariantCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ KotlinCodeOracle.class_name(ci, self.v.name())
+ }
+
+ fn canonical_name(&self) -> String {
+ self.v.name().to_string()
+ }
+}
+
+impl AsCodeType for Variant {
+ fn as_codetype(&self) -> Box<dyn CodeType> {
+ Box::new(VariantCodeType { v: self.clone() })
+ }
+}
+
+impl AsCodeType for &Variant {
+ fn as_codetype(&self) -> Box<dyn CodeType> {
+ Box::new(VariantCodeType { v: (*self).clone() })
+ }
+}
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..466fe77879
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.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 anyhow::Result;
+use camino::{Utf8Path, Utf8PathBuf};
+use fs_err as fs;
+use std::process::Command;
+
+pub mod gen_kotlin;
+pub use gen_kotlin::{generate_bindings, Config};
+mod test;
+
+use super::super::interface::ComponentInterface;
+pub use test::{run_script, 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()));
+ fs::write(&kt_file, 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: {e:?}",
+ kt_file.file_name().unwrap(),
+ );
+ }
+ }
+ 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/Async.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
new file mode 100644
index 0000000000..c6a32655f2
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
@@ -0,0 +1,44 @@
+// Async return type handlers
+
+internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort()
+internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort()
+
+internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>()
+
+// FFI type for Rust future continuations
+internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType {
+ override fun callback(continuationHandle: USize, pollResult: Short) {
+ uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult)
+ }
+
+ internal fun register(lib: _UniFFILib) {
+ lib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(this)
+ }
+}
+
+internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
+ rustFuture: Pointer,
+ pollFunc: (Pointer, USize) -> Unit,
+ completeFunc: (Pointer, RustCallStatus) -> F,
+ freeFunc: (Pointer) -> Unit,
+ liftFunc: (F) -> T,
+ errorHandler: CallStatusErrorHandler<E>
+): T {
+ try {
+ do {
+ val pollResult = suspendCancellableCoroutine<Short> { continuation ->
+ pollFunc(
+ rustFuture,
+ uniffiContinuationHandleMap.insert(continuation)
+ )
+ }
+ } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY);
+
+ return liftFunc(
+ rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
+ )
+ } finally {
+ freeFunc(rustFuture)
+ }
+}
+
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/ByteArrayHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
new file mode 100644
index 0000000000..4840a199b4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
@@ -0,0 +1,15 @@
+public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> {
+ override fun read(buf: ByteBuffer): ByteArray {
+ val len = buf.getInt()
+ val byteArr = ByteArray(len)
+ buf.get(byteArr)
+ return byteArr
+ }
+ override fun allocationSize(value: ByteArray): Int {
+ return 4 + value.size
+ }
+ override fun write(value: ByteArray, buf: ByteBuffer) {
+ buf.putInt(value.size)
+ buf.put(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..62a71e02f1
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
@@ -0,0 +1,78 @@
+internal typealias Handle = Long
+internal class ConcurrentHandleMap<T>(
+ private val leftMap: MutableMap<Handle, T> = mutableMapOf(),
+ private val rightMap: MutableMap<T, Handle> = mutableMapOf()
+) {
+ private val lock = java.util.concurrent.locks.ReentrantLock()
+ private val currentHandle = AtomicLong(0L)
+ private val stride = 1L
+
+ fun insert(obj: T): Handle =
+ lock.withLock {
+ rightMap[obj] ?:
+ currentHandle.getAndAdd(stride)
+ .also { handle ->
+ leftMap[handle] = obj
+ rightMap[obj] = handle
+ }
+ }
+
+ fun get(handle: Handle) = lock.withLock {
+ leftMap[handle]
+ }
+
+ fun delete(handle: Handle) {
+ this.remove(handle)
+ }
+
+ fun remove(handle: Handle): T? =
+ lock.withLock {
+ leftMap.remove(handle)?.let { obj ->
+ rightMap.remove(obj)
+ obj
+ }
+ }
+}
+
+interface ForeignCallback : com.sun.jna.Callback {
+ public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int
+}
+
+// Magic number for the Rust proxy to call using the same mechanism as every other method,
+// to free the callback once it's dropped by Rust.
+internal const val IDX_CALLBACK_FREE = 0
+// Callback return codes
+internal const val UNIFFI_CALLBACK_SUCCESS = 0
+internal const val UNIFFI_CALLBACK_ERROR = 1
+internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
+
+public abstract class FfiConverterCallbackInterface<CallbackInterface>(
+ protected val foreignCallback: ForeignCallback
+): FfiConverter<CallbackInterface, Handle> {
+ private val handleMap = ConcurrentHandleMap<CallbackInterface>()
+
+ // Registers the foreign callback with the Rust side.
+ // This method is generated for each callback interface.
+ internal abstract fun register(lib: _UniFFILib)
+
+ 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..5a29f0acc3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
@@ -0,0 +1,129 @@
+{%- let cbi = ci|get_callback_interface_definition(name) %}
+{%- let type_name = cbi|type_name(ci) %}
+{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %}
+
+{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
+{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
+{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }}
+{{- self.add_import("kotlin.concurrent.withLock") }}
+
+// Declaration and FfiConverters for {{ type_name }} Callback Interface
+
+public interface {{ type_name }} {
+ {% for meth in cbi.methods() -%}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+ companion object
+}
+
+// The ForeignCallback that is passed to Rust.
+internal class {{ foreign_callback }} : ForeignCallback {
+ @Suppress("TooGenericExceptionCaught")
+ override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
+ val cb = {{ ffi_converter_name }}.lift(handle)
+ return when (method) {
+ IDX_CALLBACK_FREE -> {
+ {{ ffi_converter_name }}.drop(handle)
+ // Successful return
+ // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
+ UNIFFI_CALLBACK_SUCCESS
+ }
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
+ {{ loop.index }} -> {
+ // Call the method, write to outBuf and return a status code
+ // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info
+ try {
+ this.{{ method_name }}(cb, argsData, argsLen, outBuf)
+ } catch (e: Throwable) {
+ // Unexpected error
+ try {
+ // Try to serialize the error into a string
+ outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString()))
+ } catch (e: Throwable) {
+ // If that fails, then it's time to give up and just return
+ }
+ UNIFFI_CALLBACK_UNEXPECTED_ERROR
+ }
+ }
+ {% endfor %}
+ else -> {
+ // An unexpected error happened.
+ // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
+ try {
+ // Try to serialize the error into a string
+ outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index"))
+ } catch (e: Throwable) {
+ // If that fails, then it's time to give up and just return
+ }
+ UNIFFI_CALLBACK_UNEXPECTED_ERROR
+ }
+ }
+ }
+
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
+ @Suppress("UNUSED_PARAMETER")
+ private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
+ {%- if meth.arguments().len() > 0 %}
+ val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also {
+ it.order(ByteOrder.BIG_ENDIAN)
+ }
+ {%- endif %}
+
+ {%- match meth.return_type() %}
+ {%- when Some with (return_type) %}
+ fun makeCall() : Int {
+ val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}(
+ {%- for arg in meth.arguments() %}
+ {{ arg|read_fn }}(argsBuf)
+ {% if !loop.last %}, {% endif %}
+ {%- endfor %}
+ )
+ outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue))
+ return UNIFFI_CALLBACK_SUCCESS
+ }
+ {%- when None %}
+ fun makeCall() : Int {
+ kotlinCallbackInterface.{{ meth.name()|fn_name }}(
+ {%- for arg in meth.arguments() %}
+ {{ arg|read_fn }}(argsBuf)
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}
+ )
+ return UNIFFI_CALLBACK_SUCCESS
+ }
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ fun makeCallAndHandleError() : Int = makeCall()
+ {%- when Some(error_type) %}
+ fun makeCallAndHandleError() : Int = try {
+ makeCall()
+ } catch (e: {{ error_type|type_name(ci) }}) {
+ // Expected error, serialize it into outBuf
+ outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e))
+ UNIFFI_CALLBACK_ERROR
+ }
+ {%- endmatch %}
+
+ return makeCallAndHandleError()
+ }
+ {% endfor %}
+}
+
+// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust.
+public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>(
+ foreignCallback = {{ foreign_callback }}()
+) {
+ override fun register(lib: _UniFFILib) {
+ rustCall() { status ->
+ lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status)
+ }
+ }
+}
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..04150c5d78
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
@@ -0,0 +1,62 @@
+{%- match 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(ci) }}
+public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }}
+
+{%- when Some with (config) %}
+
+{%- let ffi_type_name=builtin|ffi_type|ffi_type_name_by_value %}
+
+{# 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..d4c4a1684a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
@@ -0,0 +1,110 @@
+{#
+// 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.
+#}
+
+{%- if e.is_flat() %}
+
+enum class {{ type_name }} {
+ {% for variant in e.variants() -%}
+ {{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %}
+ {%- endfor %}
+ companion object
+}
+
+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|type_name(ci) }} : {{ type_name }}()
+ {% else -%}
+ data class {{ variant|type_name(ci) }}(
+ {% for field in variant.fields() -%}
+ val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
+ {% endfor -%}
+ ) : {{ type_name }}() {
+ companion object
+ }
+ {%- 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|type_name(ci) }} -> {
+ {%- 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 %}
+ companion object
+}
+
+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|type_name(ci) }}{% 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|type_name(ci) }} -> {
+ // 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|type_name(ci) }} -> {
+ 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..986db5424d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
@@ -0,0 +1,111 @@
+{%- let type_name = type_|type_name(ci) %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+
+{% 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|error_variant_name }}(message: String) : {{ type_name }}(message)
+ {% endfor %}
+
+ companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(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|error_variant_name %}
+ class {{ variant_name }}(
+ {% for field in variant.fields() -%}
+ val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% 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 }} = {{ ffi_converter_name }}.lift(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|error_variant_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|error_variant_name }}({{ Type::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|error_variant_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|error_variant_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|error_variant_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..0fade7a0bc
--- /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(module_path, namespace) %}
+{%- 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/ForeignExecutorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
new file mode 100644
index 0000000000..3544b2f9e6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
@@ -0,0 +1,83 @@
+{{ self.add_import("kotlinx.coroutines.CoroutineScope") }}
+{{ self.add_import("kotlinx.coroutines.delay") }}
+{{ self.add_import("kotlinx.coroutines.isActive") }}
+{{ self.add_import("kotlinx.coroutines.launch") }}
+
+internal const val UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0.toByte()
+internal const val UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1.toByte()
+internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0.toByte()
+internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED = 1.toByte()
+internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2.toByte()
+
+// Callback function to execute a Rust task. The Kotlin code schedules these in a coroutine then
+// invokes them.
+internal interface UniFfiRustTaskCallback : com.sun.jna.Callback {
+ fun callback(rustTaskData: Pointer?, statusCode: Byte)
+}
+
+internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback {
+ fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte {
+ if (rustTask == null) {
+ FfiConverterForeignExecutor.drop(handle)
+ return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
+ } else {
+ val coroutineScope = FfiConverterForeignExecutor.lift(handle)
+ if (coroutineScope.isActive) {
+ val job = coroutineScope.launch {
+ if (delayMs > 0) {
+ delay(delayMs.toLong())
+ }
+ rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS)
+ }
+ job.invokeOnCompletion { cause ->
+ if (cause != null) {
+ rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_CANCELLED)
+ }
+ }
+ return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
+ } else {
+ return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED
+ }
+ }
+ }
+}
+
+public object FfiConverterForeignExecutor: FfiConverter<CoroutineScope, USize> {
+ internal val handleMap = UniFfiHandleMap<CoroutineScope>()
+
+ internal fun drop(handle: USize) {
+ handleMap.remove(handle)
+ }
+
+ internal fun register(lib: _UniFFILib) {
+ {%- match ci.ffi_foreign_executor_callback_set() %}
+ {%- when Some with (fn) %}
+ lib.{{ fn.name() }}(UniFfiForeignExecutorCallback)
+ {%- when None %}
+ {#- No foreign executor, we don't set anything #}
+ {% endmatch %}
+ }
+
+ // Number of live handles, exposed so we can test the memory management
+ public fun handleCount() : Int {
+ return handleMap.size
+ }
+
+ override fun allocationSize(value: CoroutineScope) = USize.size
+
+ override fun lift(value: USize): CoroutineScope {
+ return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift")
+ }
+
+ override fun read(buf: ByteBuffer): CoroutineScope {
+ return lift(USize.readFromBuffer(buf))
+ }
+
+ override fun lower(value: CoroutineScope): USize {
+ return handleMap.insert(value)
+ }
+
+ override fun write(value: CoroutineScope, buf: ByteBuffer) {
+ lower(value).writeToBuffer(buf)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
new file mode 100644
index 0000000000..382a5f7413
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
@@ -0,0 +1,161 @@
+// 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: Byte = 0
+ @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
+
+ class ByValue: RustCallStatus(), Structure.ByValue
+
+ fun isSuccess(): Boolean {
+ return code == 0.toByte()
+ }
+
+ fun isError(): Boolean {
+ return code == 1.toByte()
+ }
+
+ fun isPanic(): Boolean {
+ return code == 2.toByte()
+ }
+}
+
+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)
+ checkCallStatus(errorHandler, status)
+ return return_value
+}
+
+// Check RustCallStatus and throw an error if the call wasn't successful
+private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E>, status: RustCallStatus) {
+ if (status.isSuccess()) {
+ return
+ } 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({{ Type::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);
+}
+
+// IntegerType that matches Rust's `usize` / C's `size_t`
+public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) {
+ // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin.
+ override fun toByte() = toInt().toByte()
+ // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed.
+ @Deprecated("`toInt().toChar()` is deprecated")
+ override fun toChar() = toInt().toChar()
+ override fun toShort() = toInt().toShort()
+
+ fun writeToBuffer(buf: ByteBuffer) {
+ // Make sure we always write usize integers using native byte-order, since they may be
+ // casted to pointer values
+ buf.order(ByteOrder.nativeOrder())
+ try {
+ when (Native.SIZE_T_SIZE) {
+ 4 -> buf.putInt(toInt())
+ 8 -> buf.putLong(toLong())
+ else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
+ }
+ } finally {
+ buf.order(ByteOrder.BIG_ENDIAN)
+ }
+ }
+
+ companion object {
+ val size: Int
+ get() = Native.SIZE_T_SIZE
+
+ fun readFromBuffer(buf: ByteBuffer) : USize {
+ // Make sure we always read usize integers using native byte-order, since they may be
+ // casted from pointer values
+ buf.order(ByteOrder.nativeOrder())
+ try {
+ return when (Native.SIZE_T_SIZE) {
+ 4 -> USize(buf.getInt().toLong())
+ 8 -> USize(buf.getLong())
+ else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
+ }
+ } finally {
+ buf.order(ByteOrder.BIG_ENDIAN)
+ }
+ }
+ }
+}
+
+
+// Map handles to objects
+//
+// This is used when the Rust code expects an opaque pointer to represent some foreign object.
+// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an
+// object reference , nor does it support leaking a reference to Rust.
+//
+// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to
+// Rust when it needs an opaque pointer.
+//
+// TODO: refactor callbacks to use this class
+internal class UniFfiHandleMap<T: Any> {
+ private val map = ConcurrentHashMap<USize, T>()
+ // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible
+ // values seems like enough. If somehow we generate 4 billion handles, then this will wrap
+ // around back to zero and we can assume the first handle generated will have been dropped by
+ // then.
+ private val counter = java.util.concurrent.atomic.AtomicInteger(0)
+
+ val size: Int
+ get() = map.size
+
+ fun insert(obj: T): USize {
+ val handle = USize(counter.getAndAdd(1).toLong())
+ map.put(handle, obj)
+ return handle
+ }
+
+ fun get(handle: USize): T? {
+ return map.get(handle)
+ }
+
+ fun remove(handle: USize): T? {
+ return map.remove(handle)
+ }
+}
+
+// FFI type for Rust future continuations
+internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback {
+ fun callback(continuationHandle: USize, pollResult: Short);
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
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..776c402727
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
@@ -0,0 +1,34 @@
+{%- let key_type_name = key_type|type_name(ci) %}
+{%- let value_type_name = value_type|type_name(ci) %}
+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 }}> {
+ val len = buf.getInt()
+ return buildMap<{{ key_type_name }}, {{ value_type_name }}>(len) {
+ repeat(len) {
+ val k = {{ key_type|read_fn }}(buf)
+ val v = {{ value_type|read_fn }}(buf)
+ this[k] = v
+ }
+ }
+ }
+
+ 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..6a3aeada35
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
@@ -0,0 +1,57 @@
+@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() }}")
+ .also { lib: _UniFFILib ->
+ uniffiCheckContractApiVersion(lib)
+ uniffiCheckApiChecksums(lib)
+ {% for fn in self.initialization_fns() -%}
+ {{ fn }}(lib)
+ {% endfor -%}
+ }
+ }
+ }
+
+ {% 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 (return_type) %}{{ return_type.borrow()|ffi_type_name_by_value }}{% when None %}Unit{% endmatch %}
+ {% endfor %}
+}
+
+private fun uniffiCheckContractApiVersion(lib: _UniFFILib) {
+ // Get the bindings contract version from our ComponentInterface
+ val bindings_contract_version = {{ ci.uniffi_contract_version() }}
+ // Get the scaffolding contract version by calling the into the dylib
+ val scaffolding_contract_version = lib.{{ ci.ffi_uniffi_contract_version().name() }}()
+ if (bindings_contract_version != scaffolding_contract_version) {
+ throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project")
+ }
+}
+
+@Suppress("UNUSED_PARAMETER")
+private fun uniffiCheckApiChecksums(lib: _UniFFILib) {
+ {%- for (name, expected_checksum) in ci.iter_checksums() %}
+ if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) {
+ throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
+ }
+ {%- 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..8ce27a5d04
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
@@ -0,0 +1,138 @@
+{%- let obj = ci|get_object_definition(name) %}
+{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %}
+{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
+{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+
+public interface {{ type_name }}Interface {
+ {% for meth in obj.methods() -%}
+ {%- match meth.throws_type() -%}
+ {%- when Some with (throwable) -%}
+ @Throws({{ throwable|type_name(ci) }}::class)
+ {%- when None -%}
+ {%- endmatch %}
+ {% if meth.is_async() -%}
+ suspend fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- else -%}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
+ {%- endif %}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
+ {%- when None -%}
+ {%- endmatch -%}
+
+ {% endfor %}
+ companion object
+}
+
+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(ci) }}::class)
+ {%- else -%}
+ {%- endmatch -%}
+ {%- if meth.is_async() %}
+ @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+ override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
+ return uniffiRustCallAsync(
+ callWithPointer { thisPtr ->
+ _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}(
+ thisPtr,
+ {% call kt::arg_list_lowered(meth) %}
+ )
+ },
+ {{ meth|async_poll(ci) }},
+ {{ meth|async_complete(ci) }},
+ {{ meth|async_free(ci) }},
+ // lift function
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ { {{ return_type|lift_fn }}(it) },
+ {%- when None %}
+ { Unit },
+ {% endmatch %}
+ // Error FFI converter
+ {%- match meth.throws_type() %}
+ {%- when Some(e) %}
+ {{ e|type_name(ci) }}.ErrorHandler,
+ {%- when None %}
+ NullCallStatusErrorHandler,
+ {%- endmatch %}
+ )
+ }
+ {%- else -%}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} =
+ callWithPointer {
+ {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ }.let {
+ {{ return_type|lift_fn }}(it)
+ }
+
+ {%- 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 %}
+ {% endif %}
+ {% 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 %}
+ }
+ {% else %}
+ companion object
+ {% 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..56cb5f87a5
--- /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(ci) %}
+
+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..b588ca1398
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
@@ -0,0 +1,42 @@
+{%- let rec = ci|get_record_definition(name) %}
+
+data class {{ type_name }} (
+ {%- for field in rec.fields() %}
+ var {{ field.name()|var_name }}: {{ field|type_name(ci) -}}
+ {%- match field.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }}
+ {%- 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 %}
+ companion object
+}
+
+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..dfbea24074
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
@@ -0,0 +1,87 @@
+// 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 create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue {
+ var buf = RustBuffer.ByValue()
+ buf.capacity = capacity
+ buf.len = len
+ buf.data = data
+ return buf
+ }
+
+ 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)
+ }
+
+ /**
+ * Get a `RustBuffer.ByValue` from this reference.
+ */
+ fun getValue(): RustBuffer.ByValue {
+ val pointer = getPointer()
+ val value = RustBuffer.ByValue()
+ value.writeField("capacity", pointer.getInt(0))
+ value.writeField("len", pointer.getInt(4))
+ value.writeField("data", pointer.getPointer(8))
+
+ return value
+ }
+}
+
+// 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..876d1bc05e
--- /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(ci) %}
+
+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..68324be4f9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
@@ -0,0 +1,53 @@
+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)
+ }
+
+ fun toUtf8(value: String): ByteBuffer {
+ // Make sure we don't have invalid UTF-16, check for lone surrogates.
+ return Charsets.UTF_8.newEncoder().run {
+ onMalformedInput(CodingErrorAction.REPORT)
+ encode(CharBuffer.wrap(value))
+ }
+ }
+
+ override fun lower(value: String): RustBuffer.ByValue {
+ val byteBuf = toUtf8(value)
+ // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
+ // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
+ val rbuf = RustBuffer.alloc(byteBuf.limit())
+ rbuf.asByteBuffer()!!.put(byteBuf)
+ return rbuf
+ }
+
+ // We aren't sure exactly how many bytes our string will be once it's UTF-8
+ // encoded. Allocate 3 bytes per UTF-16 code unit which will always be
+ // enough.
+ override fun allocationSize(value: String): Int {
+ val sizeForLength = 4
+ val sizeForString = value.length * 3
+ return sizeForLength + sizeForString
+ }
+
+ override fun write(value: String, buf: ByteBuffer) {
+ val byteBuf = toUtf8(value)
+ buf.putInt(byteBuf.limit())
+ buf.put(byteBuf)
+ }
+}
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..6a841d3484
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
@@ -0,0 +1,51 @@
+{%- if func.is_async() %}
+{%- match func.throws_type() -%}
+{%- when Some with (throwable) %}
+@Throws({{ throwable|type_name(ci) }}::class)
+{%- else -%}
+{%- endmatch %}
+
+@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
+ return uniffiRustCallAsync(
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}),
+ {{ func|async_poll(ci) }},
+ {{ func|async_complete(ci) }},
+ {{ func|async_free(ci) }},
+ // lift function
+ {%- match func.return_type() %}
+ {%- when Some(return_type) %}
+ { {{ return_type|lift_fn }}(it) },
+ {%- when None %}
+ { Unit },
+ {% endmatch %}
+ // Error FFI converter
+ {%- match func.throws_type() %}
+ {%- when Some(e) %}
+ {{ e|type_name(ci) }}.ErrorHandler,
+ {%- when None %}
+ NullCallStatusErrorHandler,
+ {%- endmatch %}
+ )
+}
+
+{%- else %}
+{%- match func.throws_type() -%}
+{%- when Some with (throwable) %}
+@Throws({{ throwable|type_name(ci) }}::class)
+{%- else -%}
+{%- endmatch -%}
+
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} {
+ return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %})
+}
+{% when None %}
+
+fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) =
+ {% call kt::to_ffi_call(func) %}
+
+{% endmatch %}
+{%- endif %}
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..103d444ea3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
@@ -0,0 +1,109 @@
+{%- import "macros.kt" as kt %}
+
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name(ci) %}
+{%- 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::Bytes %}
+{%- include "ByteArrayHelper.kt" %}
+
+{%- when Type::Enum { name, module_path } %}
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+{%- if !ci.is_name_used_as_error(name) %}
+{% include "EnumTemplate.kt" %}
+{%- else %}
+{% include "ErrorTemplate.kt" %}
+{%- endif -%}
+
+{%- when Type::Object { module_path, name, imp } %}
+{% include "ObjectTemplate.kt" %}
+
+{%- when Type::Record { name, module_path } %}
+{% 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 { module_path, name } %}
+{% include "CallbackInterfaceTemplate.kt" %}
+
+{%- when Type::ForeignExecutor %}
+{% include "ForeignExecutorTemplate.kt" %}
+
+{%- when Type::Timestamp %}
+{% include "TimestampHelper.kt" %}
+
+{%- when Type::Duration %}
+{% include "DurationHelper.kt" %}
+
+{%- when Type::Custom { module_path, name, builtin } %}
+{% include "CustomTypeTemplate.kt" %}
+
+{%- when Type::External { module_path, name, namespace, kind, tagged } %}
+{% include "ExternalTypeTemplate.kt" %}
+
+{%- else %}
+{%- endmatch %}
+{%- endfor %}
+
+{%- if ci.has_async_fns() %}
+{# Import types needed for async support #}
+{{ self.add_import("kotlin.coroutines.resume") }}
+{{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }}
+{{ self.add_import("kotlinx.coroutines.CancellableContinuation") }}
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
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..6a95d6a66d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
@@ -0,0 +1,77 @@
+{#
+// 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_lowered`
+#}
+
+{%- macro to_ffi_call(func) -%}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|type_name(ci) }})
+ {%- else %}
+ rustCall()
+ {%- endmatch %} { _status ->
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} _status)
+}
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) %}
+ {%- match func.throws_type() %}
+ {%- when Some with (e) %}
+ rustCallWithError({{ e|type_name(ci) }})
+ {%- else %}
+ rustCall()
+ {%- endmatch %} { _status ->
+ _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}(
+ {{- prefix }},
+ {% call arg_list_lowered(func) %}
+ _status)
+}
+{%- endmacro %}
+
+{%- macro arg_list_lowered(func) %}
+ {%- for arg in func.arguments() %}
+ {{- arg|lower_fn }}({{ arg.name()|var_name }}),
+ {%- 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(ci) }}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }}
+ {%- 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(ci) }}
+ {%- 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_by_value -}},
+ {%- endfor %}
+ {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %}
+{%- 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 -%}
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..9ee4229018
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
@@ -0,0 +1,57 @@
+// 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 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 Kotlin
+// helpers directly inline like we're doing here.
+
+import com.sun.jna.Library
+import com.sun.jna.IntegerType
+import com.sun.jna.Native
+import com.sun.jna.Pointer
+import com.sun.jna.Structure
+import com.sun.jna.Callback
+import com.sun.jna.ptr.*
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.nio.CharBuffer
+import java.nio.charset.CodingErrorAction
+import java.util.concurrent.ConcurrentHashMap
+
+{%- 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" %}
+
+// Async support
+{%- if ci.has_async_fns() %}
+{% include "Async.kt" %}
+{%- endif %}
+
+// 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..7b78540741
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
@@ -0,0 +1,131 @@
+/* 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::{
+ bindings::{RunScriptOptions, TargetLanguage},
+ library_mode::generate_bindings,
+};
+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<()> {
+ run_script(
+ tmp_dir,
+ fixture_name,
+ script_file,
+ vec![],
+ &RunScriptOptions::default(),
+ )
+}
+
+/// Run a Kotlin script
+///
+/// This function will set things up so that the script can import the UniFFI bindings for a crate
+pub fn run_script(
+ tmp_dir: &str,
+ crate_name: &str,
+ script_file: &str,
+ args: Vec<String>,
+ options: &RunScriptOptions,
+) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file);
+ let test_helper = UniFFITestHelper::new(crate_name)?;
+ let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
+ let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
+ generate_bindings(
+ &cdylib_path,
+ None,
+ &[TargetLanguage::Kotlin],
+ &out_dir,
+ false,
+ )?;
+ let jar_file = build_jar(crate_name, &out_dir, options)?;
+
+ let mut command = kotlinc_command(options);
+ command
+ .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)
+ .args(if args.is_empty() {
+ vec![]
+ } else {
+ std::iter::once(String::from("--")).chain(args).collect()
+ });
+
+ let status = command
+ .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(())
+}
+
+/// Generate kotlin bindings for the given namespace, then use the kotlin
+/// command-line tools to compile them into a .jar file.
+fn build_jar(
+ crate_name: &str,
+ out_dir: &Utf8Path,
+ options: &RunScriptOptions,
+) -> Result<Utf8PathBuf> {
+ let mut jar_file = Utf8PathBuf::from(out_dir);
+ jar_file.push(format!("{crate_name}.jar"));
+ 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}")
+ }
+
+ let mut command = kotlinc_command(options);
+ command
+ // 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);
+
+ let status = command
+ .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 kotlinc_command(options: &RunScriptOptions) -> Command {
+ let mut command = Command::new("kotlinc");
+ if !options.show_compiler_messages {
+ command.arg("-nowarn");
+ }
+ command
+}
+
+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(":")
+}