summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/bindings/kotlin
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /third_party/rust/uniffi_bindgen/src/bindings/kotlin
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs115
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs251
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs10
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt107
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt117
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt65
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt140
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt30
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt83
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt152
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt14
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt61
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt26
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt161
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt343
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md13
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt39
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt10
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt52
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt140
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs16
52 files changed, 1323 insertions, 910 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
index e20020e87c..ae4bffc973 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
@@ -26,6 +26,6 @@ impl CodeType for CallbackInterfaceCodeType {
}
fn initialization_fn(&self) -> Option<String> {
- Some(format!("{}.register", self.ffi_converter_name()))
+ Some(format!("uniffiCallbackInterface{}.register", self.id))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
index 4329f32f4c..8d075bbedb 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
@@ -5,55 +5,81 @@
use super::{AsCodeType, CodeType};
use crate::backend::{Literal, Type};
use crate::ComponentInterface;
-use paste::paste;
-fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String {
- match literal {
- Literal::Null => "null".into(),
- Literal::EmptySequence => "listOf()".into(),
- Literal::EmptyMap => "mapOf()".into(),
+#[derive(Debug)]
+pub struct OptionalCodeType {
+ inner: Type,
+}
- // For optionals
- _ => super::KotlinCodeOracle.find(inner).literal(literal, ci),
+impl OptionalCodeType {
+ pub fn new(inner: Type) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &Type {
+ &self.inner
}
}
-macro_rules! impl_code_type_for_compound {
- ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => {
- paste! {
- #[derive(Debug)]
- pub struct $T {
- inner: Type,
- }
-
- impl $T {
- pub fn new(inner: Type) -> Self {
- Self { inner }
- }
- fn inner(&self) -> &Type {
- &self.inner
- }
- }
-
- impl CodeType for $T {
- fn type_label(&self, ci: &ComponentInterface) -> String {
- format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label(ci))
- }
-
- fn canonical_name(&self) -> String {
- format!($canonical_name_pattern, super::KotlinCodeOracle.find(self.inner()).canonical_name())
- }
-
- fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
- render_literal(literal, self.inner(), ci)
- }
- }
+impl CodeType for OptionalCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ format!(
+ "{}?",
+ super::KotlinCodeOracle.find(self.inner()).type_label(ci)
+ )
+ }
+
+ fn canonical_name(&self) -> String {
+ format!(
+ "Optional{}",
+ super::KotlinCodeOracle.find(self.inner()).canonical_name()
+ )
+ }
+
+ fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::None => "null".into(),
+ Literal::Some { inner } => super::KotlinCodeOracle.find(&self.inner).literal(inner, ci),
+ _ => panic!("Invalid literal for Optional type: {literal:?}"),
}
}
- }
+}
-impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}");
-impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}");
+#[derive(Debug)]
+pub struct SequenceCodeType {
+ inner: Type,
+}
+
+impl SequenceCodeType {
+ pub fn new(inner: Type) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &Type {
+ &self.inner
+ }
+}
+
+impl CodeType for SequenceCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ format!(
+ "List<{}>",
+ super::KotlinCodeOracle.find(self.inner()).type_label(ci)
+ )
+ }
+
+ fn canonical_name(&self) -> String {
+ format!(
+ "Sequence{}",
+ super::KotlinCodeOracle.find(self.inner()).canonical_name()
+ )
+ }
+
+ fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::EmptySequence => "listOf()".into(),
+ _ => panic!("Invalid literal for List type: {literal:?}"),
+ }
+ }
+}
#[derive(Debug)]
pub struct MapCodeType {
@@ -92,7 +118,10 @@ impl CodeType for MapCodeType {
)
}
- fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
- render_literal(literal, &self.value, ci)
+ fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::EmptyMap => "mapOf()".into(),
+ _ => panic!("Invalid literal for Map type: {literal:?}"),
+ }
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs
deleted file mode 100644
index 154e12a381..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use super::CodeType;
-use crate::ComponentInterface;
-
-#[derive(Debug)]
-pub struct ForeignExecutorCodeType;
-
-impl CodeType for ForeignExecutorCodeType {
- fn type_label(&self, _ci: &ComponentInterface) -> String {
- // Kotlin uses a CoroutineScope for ForeignExecutor
- "CoroutineScope".into()
- }
-
- fn canonical_name(&self) -> String {
- "ForeignExecutor".into()
- }
-
- fn initialization_fn(&self) -> Option<String> {
- Some("FfiConverterForeignExecutor.register".into())
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
index 3ecf09d47f..d55c78f760 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
@@ -17,8 +17,8 @@ impl ExternalCodeType {
}
impl CodeType for ExternalCodeType {
- fn type_label(&self, _ci: &ComponentInterface) -> String {
- self.name.clone()
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ super::KotlinCodeOracle.class_name(ci, &self.name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
index 1ed0575a9a..c4fc8e0ed6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
@@ -7,20 +7,21 @@ use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::Debug;
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use askama::Template;
+use camino::Utf8Path;
use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
use serde::{Deserialize, Serialize};
use crate::backend::TemplateExpression;
+use crate::bindings::kotlin;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
mod callback_interface;
mod compounds;
mod custom;
mod enum_;
-mod executor;
mod external;
mod miscellany;
mod object;
@@ -28,6 +29,28 @@ mod primitives;
mod record;
mod variant;
+pub struct KotlinBindingGenerator;
+impl BindingGenerator for KotlinBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ kotlin::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> {
+ if cdylib_name.is_none() {
+ bail!("Generate bindings for Kotlin requires a cdylib, but {library_path} was given");
+ }
+ Ok(())
+ }
+}
+
trait CodeType: Debug {
/// The language specific label used to reference this type. This will be used in
/// method signatures and property declarations.
@@ -73,10 +96,21 @@ trait CodeType: Debug {
pub struct Config {
package_name: Option<String>,
cdylib_name: Option<String>,
+ generate_immutable_records: Option<bool>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
#[serde(default)]
external_packages: HashMap<String, String>,
+ #[serde(default)]
+ android: bool,
+ #[serde(default)]
+ android_cleaner: Option<bool>,
+}
+
+impl Config {
+ pub(crate) fn android_cleaner(&self) -> bool {
+ self.android_cleaner.unwrap_or(self.android)
+ }
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
@@ -103,6 +137,11 @@ impl Config {
"uniffi".into()
}
}
+
+ /// Whether to generate immutable records (`val` instead of `var`)
+ pub fn generate_immutable_records(&self) -> bool {
+ self.generate_immutable_records.unwrap_or(false)
+ }
}
impl BindingsConfig for Config {
@@ -236,7 +275,6 @@ pub struct KotlinWrapper<'a> {
ci: &'a ComponentInterface,
type_helper_code: String,
type_imports: BTreeSet<ImportRequirement>,
- has_async_fns: bool,
}
impl<'a> KotlinWrapper<'a> {
@@ -249,7 +287,6 @@ impl<'a> KotlinWrapper<'a> {
ci,
type_helper_code,
type_imports,
- has_async_fns: ci.has_async_fns(),
}
}
@@ -258,10 +295,6 @@ impl<'a> KotlinWrapper<'a> {
.iter_types()
.map(|t| KotlinCodeOracle.find(t))
.filter_map(|ct| ct.initialization_fn())
- .chain(
- self.has_async_fns
- .then(|| "uniffiRustFutureContinuationCallback.register".into()),
- )
.collect()
}
@@ -301,7 +334,12 @@ impl KotlinCodeOracle {
/// Get the idiomatic Kotlin rendering of a variable name.
fn var_name(&self, nm: &str) -> String {
- format!("`{}`", nm.to_string().to_lower_camel_case())
+ format!("`{}`", self.var_name_raw(nm))
+ }
+
+ /// `var_name` without the backticks. Useful for using in `@Structure.FieldOrder`.
+ pub fn var_name_raw(&self, nm: &str) -> String {
+ nm.to_string().to_lower_camel_case()
}
/// Get the idiomatic Kotlin rendering of an individual enum variant.
@@ -309,14 +347,78 @@ impl KotlinCodeOracle {
nm.to_string().to_shouty_snake_case()
}
- fn ffi_type_label_by_value(ffi_type: &FfiType) -> String {
+ /// Get the idiomatic Kotlin rendering of an FFI callback function name
+ fn ffi_callback_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Kotlin rendering of an FFI struct name
+ fn ffi_struct_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ fn ffi_type_label_by_value(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type)),
+ FfiType::Struct(name) => format!("{}.UniffiByValue", self.ffi_struct_name(name)),
+ _ => self.ffi_type_label(ffi_type),
+ }
+ }
+
+ /// FFI type name to use inside structs
+ ///
+ /// The main requirement here is that all types must have default values or else the struct
+ /// won't work in some JNA contexts.
+ fn ffi_type_label_for_ffi_struct(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ // Make callbacks function pointers nullable. This matches the semantics of a C
+ // function pointer better and allows for `null` as a default value.
+ FfiType::Callback(name) => format!("{}?", self.ffi_callback_name(name)),
+ _ => self.ffi_type_label_by_value(ffi_type),
+ }
+ }
+
+ /// Default values for FFI
+ ///
+ /// This is used to:
+ /// - Set a default return value for error results
+ /// - Set a default for structs, which JNA sometimes requires
+ fn ffi_default_value(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::UInt8 | FfiType::Int8 => "0.toByte()".to_owned(),
+ FfiType::UInt16 | FfiType::Int16 => "0.toShort()".to_owned(),
+ FfiType::UInt32 | FfiType::Int32 => "0".to_owned(),
+ FfiType::UInt64 | FfiType::Int64 => "0.toLong()".to_owned(),
+ FfiType::Float32 => "0.0f".to_owned(),
+ FfiType::Float64 => "0.0".to_owned(),
+ FfiType::RustArcPtr(_) => "Pointer.NULL".to_owned(),
+ FfiType::RustBuffer(_) => "RustBuffer.ByValue()".to_owned(),
+ FfiType::Callback(_) => "null".to_owned(),
+ FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue()".to_owned(),
+ _ => unimplemented!("ffi_default_value: {ffi_type:?}"),
+ }
+ }
+
+ fn ffi_type_label_by_reference(&self, ffi_type: &FfiType) -> String {
match ffi_type {
- FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)),
- _ => Self::ffi_type_label(ffi_type),
+ FfiType::Int8
+ | FfiType::UInt8
+ | FfiType::Int16
+ | FfiType::UInt16
+ | FfiType::Int32
+ | FfiType::UInt32
+ | FfiType::Int64
+ | FfiType::UInt64
+ | FfiType::Float32
+ | FfiType::Float64 => format!("{}ByReference", self.ffi_type_label(ffi_type)),
+ FfiType::RustArcPtr(_) => "PointerByReference".to_owned(),
+ // JNA structs default to ByReference
+ FfiType::RustBuffer(_) | FfiType::Struct(_) => self.ffi_type_label(ffi_type),
+ _ => panic!("{ffi_type:?} by reference is not implemented"),
}
}
- fn ffi_type_label(ffi_type: &FfiType) -> String {
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
// Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not
// support them yet. Thus, we use the signed variants to represent both signed and unsigned
@@ -327,19 +429,35 @@ impl KotlinCodeOracle {
FfiType::Int64 | FfiType::UInt64 => "Long".to_string(),
FfiType::Float32 => "Float".to_string(),
FfiType::Float64 => "Double".to_string(),
+ FfiType::Handle => "Long".to_string(),
FfiType::RustArcPtr(_) => "Pointer".to_string(),
FfiType::RustBuffer(maybe_suffix) => {
format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
}
+ FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue".to_string(),
FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
- FfiType::ForeignCallback => "ForeignCallback".to_string(),
- FfiType::ForeignExecutorHandle => "USize".to_string(),
- FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(),
- FfiType::RustFutureHandle => "Pointer".to_string(),
- FfiType::RustFutureContinuationCallback => {
- "UniFffiRustFutureContinuationCallbackType".to_string()
- }
- FfiType::RustFutureContinuationData => "USize".to_string(),
+ FfiType::Callback(name) => self.ffi_callback_name(name),
+ FfiType::Struct(name) => self.ffi_struct_name(name),
+ FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner),
+ FfiType::VoidPointer => "Pointer".to_string(),
+ }
+ }
+
+ /// Get the name of the interface and class name for an object.
+ ///
+ /// If we support callback interfaces, the interface name is the object name, and the class name is derived from that.
+ /// Otherwise, the class name is the object name and the interface name is derived from that.
+ ///
+ /// This split determines what types `FfiConverter.lower()` inputs. If we support callback
+ /// interfaces, `lower` must lower anything that implements the interface. If not, then lower
+ /// only lowers the concrete class.
+ fn object_names(&self, ci: &ComponentInterface, obj: &Object) -> (String, String) {
+ let class_name = self.class_name(ci, obj.name());
+ if obj.has_callback_interface() {
+ let impl_name = format!("{class_name}Impl");
+ (class_name, impl_name)
+ } else {
+ (format!("{class_name}Interface"), class_name)
}
}
}
@@ -376,12 +494,11 @@ impl<T: AsType> AsCodeType for T {
Type::Duration => Box::new(miscellany::DurationCodeType),
Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
- Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
+ Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)),
Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
Type::CallbackInterface { name, .. } => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
}
- Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional { inner_type } => {
Box::new(compounds::OptionalCodeType::new(*inner_type))
}
@@ -401,6 +518,7 @@ impl<T: AsType> AsCodeType for T {
mod filters {
use super::*;
pub use crate::backend::filters::*;
+ use uniffi_meta::LiteralMetadata;
pub(super) fn type_name(
as_ct: &impl AsCodeType,
@@ -454,8 +572,52 @@ mod filters {
Ok(as_ct.as_codetype().literal(literal, ci))
}
+ // Get the idiomatic Kotlin rendering of an integer.
+ fn int_literal(t: &Option<Type>, base10: String) -> Result<String, askama::Error> {
+ if let Some(t) = t {
+ match t {
+ Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => Ok(base10),
+ Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => Ok(base10 + "u"),
+ _ => Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Only ints are supported.".to_string(),
+ )))),
+ }
+ } else {
+ Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Enum hasn't defined a repr".to_string(),
+ ))))
+ }
+ }
+
+ // Get the idiomatic Kotlin rendering of an individual enum variant's discriminant
+ pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> {
+ let literal = e.variant_discr(*index).expect("invalid index");
+ match literal {
+ // Kotlin doesn't convert between signed and unsigned by default
+ // so we'll need to make sure we define the type as appropriately
+ LiteralMetadata::UInt(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()),
+ LiteralMetadata::Int(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()),
+ _ => Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Only ints are supported.".to_string(),
+ )))),
+ }
+ }
+
pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> {
- Ok(KotlinCodeOracle::ffi_type_label_by_value(type_))
+ Ok(KotlinCodeOracle.ffi_type_label_by_value(type_))
+ }
+
+ pub fn ffi_type_name_for_ffi_struct(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_type_label_for_ffi_struct(type_))
+ }
+
+ pub fn ffi_default_value(type_: FfiType) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_default_value(&type_))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a function name.
+ pub fn class_name(nm: &str, ci: &ComponentInterface) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.class_name(ci, nm))
}
/// Get the idiomatic Kotlin rendering of a function name.
@@ -468,6 +630,11 @@ mod filters {
Ok(KotlinCodeOracle.var_name(nm))
}
+ /// Get the idiomatic Kotlin rendering of a variable name.
+ pub fn var_name_raw(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.var_name_raw(nm))
+ }
+
/// Get a String representing the name used for an individual enum variant.
pub fn variant_name(v: &Variant) -> Result<String, askama::Error> {
Ok(KotlinCodeOracle.enum_variant_name(v.name()))
@@ -478,13 +645,30 @@ mod filters {
Ok(KotlinCodeOracle.convert_error_suffix(&name))
}
+ /// Get the idiomatic Kotlin rendering of an FFI callback function name
+ pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_callback_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of an FFI struct name
+ pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_struct_name(nm))
+ }
+
+ pub fn object_names(
+ obj: &Object,
+ ci: &ComponentInterface,
+ ) -> Result<(String, String), askama::Error> {
+ Ok(KotlinCodeOracle.object_names(ci, obj))
+ }
+
pub fn async_poll(
callable: impl Callable,
ci: &ComponentInterface,
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_poll(ci);
Ok(format!(
- "{{ future, continuation -> _UniFFILib.INSTANCE.{ffi_func}(future, continuation) }}"
+ "{{ future, callback, continuation -> UniffiLib.INSTANCE.{ffi_func}(future, callback, continuation) }}"
))
}
@@ -493,7 +677,7 @@ mod filters {
ci: &ComponentInterface,
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_complete(ci);
- let call = format!("_UniFFILib.INSTANCE.{ffi_func}(future, continuation)");
+ let call = format!("UniffiLib.INSTANCE.{ffi_func}(future, continuation)");
let call = match callable.return_type() {
Some(Type::External {
kind: ExternalKind::DataClass,
@@ -502,7 +686,7 @@ mod filters {
}) => {
// Need to convert the RustBuffer from our package to the RustBuffer of the external package
let suffix = KotlinCodeOracle.class_name(ci, &name);
- format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}")
+ format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity.toULong(), it.len.toULong(), it.data) }}")
}
_ => call,
};
@@ -515,7 +699,7 @@ mod filters {
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_free(ci);
Ok(format!(
- "{{ future -> _UniFFILib.INSTANCE.{ffi_func}(future) }}"
+ "{{ future -> UniffiLib.INSTANCE.{ffi_func}(future) }}"
))
}
@@ -527,4 +711,13 @@ mod filters {
pub fn unquote(nm: &str) -> Result<String, askama::Error> {
Ok(nm.trim_matches('`').to_string())
}
+
+ /// Get the idiomatic Kotlin rendering of docstring
+ pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
+ let middle = textwrap::indent(&textwrap::dedent(docstring), " * ");
+ let wrapped = format!("/**\n{middle}\n */");
+
+ let spaces = usize::try_from(*spaces).unwrap_or_default();
+ Ok(textwrap::indent(&wrapped, &" ".repeat(spaces)))
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
index c39ae59cce..5a4305d14a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
@@ -3,25 +3,32 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::CodeType;
-use crate::ComponentInterface;
+use crate::{interface::ObjectImpl, ComponentInterface};
#[derive(Debug)]
pub struct ObjectCodeType {
- id: String,
+ name: String,
+ imp: ObjectImpl,
}
impl ObjectCodeType {
- pub fn new(id: String) -> Self {
- Self { id }
+ pub fn new(name: String, imp: ObjectImpl) -> Self {
+ Self { name, imp }
}
}
impl CodeType for ObjectCodeType {
fn type_label(&self, ci: &ComponentInterface) -> String {
- super::KotlinCodeOracle.class_name(ci, &self.id)
+ super::KotlinCodeOracle.class_name(ci, &self.name)
}
fn canonical_name(&self) -> String {
- format!("Type{}", self.id)
+ format!("Type{}", self.name)
+ }
+
+ fn initialization_fn(&self) -> Option<String> {
+ self.imp
+ .has_callback_interface()
+ .then(|| format!("uniffiCallbackInterface{}.register", self.name))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
index 22495fa209..0bc5a5d99e 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
@@ -9,7 +9,11 @@ use paste::paste;
fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String {
fn typed_number(type_: &Type, num_str: String) -> String {
- match type_ {
+ let unwrapped_type = match type_ {
+ Type::Optional { inner_type } => inner_type,
+ t => t,
+ };
+ match unwrapped_type {
// Bytes, Shorts and Ints can all be inferred from the type.
Type::Int8 | Type::Int16 | Type::Int32 => num_str,
Type::Int64 => format!("{num_str}L"),
@@ -19,7 +23,7 @@ fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String {
Type::Float32 => format!("{num_str}f"),
Type::Float64 => num_str,
- _ => panic!("Unexpected literal: {num_str} is not a number"),
+ _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"),
}
}
@@ -56,7 +60,7 @@ macro_rules! impl_code_type_for_primitive {
impl CodeType for $T {
fn type_label(&self, _ci: &ComponentInterface) -> String {
- $class_name.into()
+ format!("kotlin.{}", $class_name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
index c6a32655f2..b28fbd2c80 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
@@ -1,44 +1,117 @@
// Async return type handlers
-internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort()
-internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort()
+internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte()
+internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte()
-internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>()
+internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>()
// FFI type for Rust future continuations
-internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType {
- override fun callback(continuationHandle: USize, pollResult: Short) {
- uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult)
- }
-
- internal fun register(lib: _UniFFILib) {
- lib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(this)
+internal object uniffiRustFutureContinuationCallbackImpl: UniffiRustFutureContinuationCallback {
+ override fun callback(data: Long, pollResult: Byte) {
+ uniffiContinuationHandleMap.remove(data).resume(pollResult)
}
}
internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
- rustFuture: Pointer,
- pollFunc: (Pointer, USize) -> Unit,
- completeFunc: (Pointer, RustCallStatus) -> F,
- freeFunc: (Pointer) -> Unit,
+ rustFuture: Long,
+ pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit,
+ completeFunc: (Long, UniffiRustCallStatus) -> F,
+ freeFunc: (Long) -> Unit,
liftFunc: (F) -> T,
- errorHandler: CallStatusErrorHandler<E>
+ errorHandler: UniffiRustCallStatusErrorHandler<E>
): T {
try {
do {
- val pollResult = suspendCancellableCoroutine<Short> { continuation ->
+ val pollResult = suspendCancellableCoroutine<Byte> { continuation ->
pollFunc(
rustFuture,
+ uniffiRustFutureContinuationCallbackImpl,
uniffiContinuationHandleMap.insert(continuation)
)
}
} while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY);
return liftFunc(
- rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
+ uniffiRustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
)
} finally {
freeFunc(rustFuture)
}
}
+{%- if ci.has_async_callback_interface_definition() %}
+internal inline fun<T> uniffiTraitInterfaceCallAsync(
+ crossinline makeCall: suspend () -> T,
+ crossinline handleSuccess: (T) -> Unit,
+ crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit,
+): UniffiForeignFuture {
+ // Using `GlobalScope` is labeled as a "delicate API" and generally discouraged in Kotlin programs, since it breaks structured concurrency.
+ // However, our parent task is a Rust future, so we're going to need to break structure concurrency in any case.
+ //
+ // Uniffi does its best to support structured concurrency across the FFI.
+ // If the Rust future is dropped, `uniffiForeignFutureFreeImpl` is called, which will cancel the Kotlin coroutine if it's still running.
+ @OptIn(DelicateCoroutinesApi::class)
+ val job = GlobalScope.launch {
+ try {
+ handleSuccess(makeCall())
+ } catch(e: Exception) {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(e.toString()),
+ )
+ )
+ }
+ }
+ val handle = uniffiForeignFutureHandleMap.insert(job)
+ return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl)
+}
+
+internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallAsyncWithError(
+ crossinline makeCall: suspend () -> T,
+ crossinline handleSuccess: (T) -> Unit,
+ crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit,
+ crossinline lowerError: (E) -> RustBuffer.ByValue,
+): UniffiForeignFuture {
+ // See uniffiTraitInterfaceCallAsync for details on `DelicateCoroutinesApi`
+ @OptIn(DelicateCoroutinesApi::class)
+ val job = GlobalScope.launch {
+ try {
+ handleSuccess(makeCall())
+ } catch(e: Exception) {
+ if (e is E) {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_ERROR,
+ lowerError(e),
+ )
+ )
+ } else {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(e.toString()),
+ )
+ )
+ }
+ }
+ }
+ val handle = uniffiForeignFutureHandleMap.insert(job)
+ return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl)
+}
+
+internal val uniffiForeignFutureHandleMap = UniffiHandleMap<Job>()
+
+internal object uniffiForeignFutureFreeImpl: UniffiForeignFutureFree {
+ override fun callback(handle: Long) {
+ val job = uniffiForeignFutureHandleMap.remove(handle)
+ if (!job.isCompleted) {
+ job.cancel()
+ }
+ }
+}
+
+// For testing
+public fun uniffiForeignFutureHandleCount() = uniffiForeignFutureHandleMap.size
+
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
index 8cfa2ce000..c6b266066d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
@@ -11,7 +11,7 @@ public object FfiConverterBoolean: FfiConverter<Boolean, Byte> {
return if (value) 1.toByte() else 0.toByte()
}
- override fun allocationSize(value: Boolean) = 1
+ override fun allocationSize(value: Boolean) = 1UL
override fun write(value: Boolean, buf: ByteBuffer) {
buf.put(lower(value))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
index 4840a199b4..c9449069e2 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
@@ -5,8 +5,8 @@ public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> {
buf.get(byteArr)
return byteArr
}
- override fun allocationSize(value: ByteArray): Int {
- return 4 + value.size
+ override fun allocationSize(value: ByteArray): ULong {
+ return 4UL + value.size.toULong()
}
override fun write(value: ByteArray, buf: ByteBuffer) {
buf.putInt(value.size)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt
new file mode 100644
index 0000000000..30a39d9afb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt
@@ -0,0 +1,117 @@
+{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
+
+{%- let trait_impl=format!("uniffiCallbackInterface{}", name) %}
+
+// Put the implementation in an object so we don't pollute the top-level namespace
+internal object {{ trait_impl }} {
+ {%- for (ffi_callback, meth) in vtable_methods.iter() %}
+ internal object {{ meth.name()|var_name }}: {{ ffi_callback.name()|ffi_callback_name }} {
+ override fun callback(
+ {%- for arg in ffi_callback.arguments() -%}
+ {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }},
+ {%- endfor -%}
+ {%- if ffi_callback.has_rust_call_status_arg() -%}
+ uniffiCallStatus: UniffiRustCallStatus,
+ {%- endif -%}
+ )
+ {%- match ffi_callback.return_type() %}
+ {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }},
+ {%- when None %}
+ {%- endmatch %} {
+ val uniffiObj = {{ ffi_converter_name }}.handleMap.get(uniffiHandle)
+ val makeCall = {% if meth.is_async() %}suspend {% endif %}{ ->
+ uniffiObj.{{ meth.name()|fn_name() }}(
+ {%- for arg in meth.arguments() %}
+ {{ arg|lift_fn }}({{ arg.name()|var_name }}),
+ {%- endfor %}
+ )
+ }
+ {%- if !meth.is_async() %}
+
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ val writeReturn = { value: {{ return_type|type_name(ci) }} -> uniffiOutReturn.setValue({{ return_type|lower_fn }}(value)) }
+ {%- when None %}
+ val writeReturn = { _: Unit -> Unit }
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn)
+ {%- when Some(error_type) %}
+ uniffiTraitInterfaceCallWithError(
+ uniffiCallStatus,
+ makeCall,
+ writeReturn,
+ { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) }
+ )
+ {%- endmatch %}
+
+ {%- else %}
+ val uniffiHandleSuccess = { {% if meth.return_type().is_some() %}returnValue{% else %}_{% endif %}: {% match meth.return_type() %}{%- when Some(return_type) %}{{ return_type|type_name(ci) }}{%- when None %}Unit{% endmatch %} ->
+ val uniffiResult = {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ return_type|lower_fn }}(returnValue),
+ {%- when None %}
+ {%- endmatch %}
+ UniffiRustCallStatus.ByValue()
+ )
+ uniffiResult.write()
+ uniffiFutureCallback.callback(uniffiCallbackData, uniffiResult)
+ }
+ val uniffiHandleError = { callStatus: UniffiRustCallStatus.ByValue ->
+ uniffiFutureCallback.callback(
+ uniffiCallbackData,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ return_type.into()|ffi_default_value }},
+ {%- when None %}
+ {%- endmatch %}
+ callStatus,
+ ),
+ )
+ }
+
+ uniffiOutReturn.uniffiSetValue(
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffiTraitInterfaceCallAsync(
+ makeCall,
+ uniffiHandleSuccess,
+ uniffiHandleError
+ )
+ {%- when Some(error_type) %}
+ uniffiTraitInterfaceCallAsyncWithError(
+ makeCall,
+ uniffiHandleSuccess,
+ uniffiHandleError,
+ { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) }
+ )
+ {%- endmatch %}
+ )
+ {%- endif %}
+ }
+ }
+ {%- endfor %}
+
+ internal object uniffiFree: {{ "CallbackInterfaceFree"|ffi_callback_name }} {
+ override fun callback(handle: Long) {
+ {{ ffi_converter_name }}.handleMap.remove(handle)
+ }
+ }
+
+ internal var vtable = {{ vtable|ffi_type_name_by_value }}(
+ {%- for (ffi_callback, meth) in vtable_methods.iter() %}
+ {{ meth.name()|var_name() }},
+ {%- endfor %}
+ uniffiFree,
+ )
+
+ // Registers the foreign callback with the Rust side.
+ // This method is generated for each callback interface.
+ internal fun register(lib: UniffiLib) {
+ lib.{{ ffi_init_callback.name() }}(vtable)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
index 62a71e02f1..d58a651e24 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
@@ -1,43 +1,3 @@
-internal typealias Handle = Long
-internal class ConcurrentHandleMap<T>(
- private val leftMap: MutableMap<Handle, T> = mutableMapOf(),
- private val rightMap: MutableMap<T, Handle> = mutableMapOf()
-) {
- private val lock = java.util.concurrent.locks.ReentrantLock()
- private val currentHandle = AtomicLong(0L)
- private val stride = 1L
-
- fun insert(obj: T): Handle =
- lock.withLock {
- rightMap[obj] ?:
- currentHandle.getAndAdd(stride)
- .also { handle ->
- leftMap[handle] = obj
- rightMap[obj] = handle
- }
- }
-
- fun get(handle: Handle) = lock.withLock {
- leftMap[handle]
- }
-
- fun delete(handle: Handle) {
- this.remove(handle)
- }
-
- fun remove(handle: Handle): T? =
- lock.withLock {
- leftMap.remove(handle)?.let { obj ->
- rightMap.remove(obj)
- obj
- }
- }
-}
-
-interface ForeignCallback : com.sun.jna.Callback {
- public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int
-}
-
// Magic number for the Rust proxy to call using the same mechanism as every other method,
// to free the callback once it's dropped by Rust.
internal const val IDX_CALLBACK_FREE = 0
@@ -46,31 +6,22 @@ internal const val UNIFFI_CALLBACK_SUCCESS = 0
internal const val UNIFFI_CALLBACK_ERROR = 1
internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
-public abstract class FfiConverterCallbackInterface<CallbackInterface>(
- protected val foreignCallback: ForeignCallback
-): FfiConverter<CallbackInterface, Handle> {
- private val handleMap = ConcurrentHandleMap<CallbackInterface>()
-
- // Registers the foreign callback with the Rust side.
- // This method is generated for each callback interface.
- internal abstract fun register(lib: _UniFFILib)
+public abstract class FfiConverterCallbackInterface<CallbackInterface: Any>: FfiConverter<CallbackInterface, Long> {
+ internal val handleMap = UniffiHandleMap<CallbackInterface>()
- fun drop(handle: Handle): RustBuffer.ByValue {
- return handleMap.remove(handle).let { RustBuffer.ByValue() }
+ internal fun drop(handle: Long) {
+ handleMap.remove(handle)
}
- override fun lift(value: Handle): CallbackInterface {
- return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug")
+ override fun lift(value: Long): CallbackInterface {
+ return handleMap.get(value)
}
override fun read(buf: ByteBuffer) = lift(buf.getLong())
- override fun lower(value: CallbackInterface) =
- handleMap.insert(value).also {
- assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." }
- }
+ override fun lower(value: CallbackInterface) = handleMap.insert(value)
- override fun allocationSize(value: CallbackInterface) = 8
+ override fun allocationSize(value: CallbackInterface) = 8UL
override fun write(value: CallbackInterface, buf: ByteBuffer) {
buf.putLong(lower(value))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
index 5a29f0acc3..d2cdee4f33 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
@@ -1,129 +1,13 @@
{%- let cbi = ci|get_callback_interface_definition(name) %}
-{%- let type_name = cbi|type_name(ci) %}
-{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %}
-
-{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
-{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
-{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }}
-{{- self.add_import("kotlin.concurrent.withLock") }}
-
-// Declaration and FfiConverters for {{ type_name }} Callback Interface
-
-public interface {{ type_name }} {
- {% for meth in cbi.methods() -%}
- fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
- {%- else -%}
- {%- endmatch %}
- {% endfor %}
- companion object
-}
-
-// The ForeignCallback that is passed to Rust.
-internal class {{ foreign_callback }} : ForeignCallback {
- @Suppress("TooGenericExceptionCaught")
- override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
- val cb = {{ ffi_converter_name }}.lift(handle)
- return when (method) {
- IDX_CALLBACK_FREE -> {
- {{ ffi_converter_name }}.drop(handle)
- // Successful return
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- UNIFFI_CALLBACK_SUCCESS
- }
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
- {{ loop.index }} -> {
- // Call the method, write to outBuf and return a status code
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info
- try {
- this.{{ method_name }}(cb, argsData, argsLen, outBuf)
- } catch (e: Throwable) {
- // Unexpected error
- try {
- // Try to serialize the error into a string
- outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString()))
- } catch (e: Throwable) {
- // If that fails, then it's time to give up and just return
- }
- UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- }
- {% endfor %}
- else -> {
- // An unexpected error happened.
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- try {
- // Try to serialize the error into a string
- outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index"))
- } catch (e: Throwable) {
- // If that fails, then it's time to give up and just return
- }
- UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- }
- }
-
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
- @Suppress("UNUSED_PARAMETER")
- private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
- {%- if meth.arguments().len() > 0 %}
- val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also {
- it.order(ByteOrder.BIG_ENDIAN)
- }
- {%- endif %}
-
- {%- match meth.return_type() %}
- {%- when Some with (return_type) %}
- fun makeCall() : Int {
- val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}(
- {%- for arg in meth.arguments() %}
- {{ arg|read_fn }}(argsBuf)
- {% if !loop.last %}, {% endif %}
- {%- endfor %}
- )
- outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue))
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- when None %}
- fun makeCall() : Int {
- kotlinCallbackInterface.{{ meth.name()|fn_name }}(
- {%- for arg in meth.arguments() %}
- {{ arg|read_fn }}(argsBuf)
- {%- if !loop.last %}, {% endif %}
- {%- endfor %}
- )
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- endmatch %}
-
- {%- match meth.throws_type() %}
- {%- when None %}
- fun makeCallAndHandleError() : Int = makeCall()
- {%- when Some(error_type) %}
- fun makeCallAndHandleError() : Int = try {
- makeCall()
- } catch (e: {{ error_type|type_name(ci) }}) {
- // Expected error, serialize it into outBuf
- outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e))
- UNIFFI_CALLBACK_ERROR
- }
- {%- endmatch %}
-
- return makeCallAndHandleError()
- }
- {% endfor %}
-}
-
-// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust.
-public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>(
- foreignCallback = {{ foreign_callback }}()
-) {
- override fun register(lib: _UniFFILib) {
- rustCall() { status ->
- lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status)
- }
- }
-}
+{%- let ffi_init_callback = cbi.ffi_init_callback() %}
+{%- let interface_name = cbi|type_name(ci) %}
+{%- let interface_docstring = cbi.docstring() %}
+{%- let methods = cbi.methods() %}
+{%- let vtable = cbi.vtable() %}
+{%- let vtable_methods = cbi.vtable_methods() %}
+
+{% include "Interface.kt" %}
+{% include "CallbackInterfaceImpl.kt" %}
+
+// The ffiConverter which transforms the Callbacks in to handles to pass to Rust.
+public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ interface_name }}>()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
index 04150c5d78..aeb5f58002 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
@@ -49,7 +49,7 @@ public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_nam
return {{ config.into_custom.render("builtinValue") }}
}
- override fun allocationSize(value: {{ name }}): Int {
+ override fun allocationSize(value: {{ name }}): ULong {
val builtinValue = {{ config.from_custom.render("value") }}
return {{ builtin|allocation_size_fn }}(builtinValue)
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
index 4237c6f9a8..62e02607f3 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
@@ -14,7 +14,7 @@ public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> {
}
// 8 bytes for seconds, 4 bytes for nanoseconds
- override fun allocationSize(value: java.time.Duration) = 12
+ override fun allocationSize(value: java.time.Duration) = 12UL
override fun write(value: java.time.Duration, buf: ByteBuffer) {
if (value.seconds < 0) {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
index d4c4a1684a..8d1c2235ec 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
@@ -7,12 +7,25 @@
{%- if e.is_flat() %}
+{%- call kt::docstring(e, 0) %}
+{% match e.variant_discr_type() %}
+{% when None %}
enum class {{ type_name }} {
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %}
{%- endfor %}
companion object
}
+{% when Some with (variant_discr_type) %}
+enum class {{ type_name }}(val value: {{ variant_discr_type|type_name(ci) }}) {
+ {% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
+ {{ variant|variant_name }}({{ e|variant_discr_literal(loop.index0) }}){% if loop.last %};{% else %},{% endif %}
+ {%- endfor %}
+ companion object
+}
+{% endmatch %}
public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
override fun read(buf: ByteBuffer) = try {
@@ -21,7 +34,7 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}
throw RuntimeException("invalid enum value, something is very wrong!!", e)
}
- override fun allocationSize(value: {{ type_name }}) = 4
+ override fun allocationSize(value: {{ type_name }}) = 4UL
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
buf.putInt(value.ordinal + 1)
@@ -30,15 +43,18 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}
{% else %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} {
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{% if !variant.has_fields() -%}
object {{ variant|type_name(ci) }} : {{ type_name }}()
{% else -%}
data class {{ variant|type_name(ci) }}(
- {% for field in variant.fields() -%}
- val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
- {% endfor -%}
+ {%- for field in variant.fields() -%}
+ {%- call kt::docstring(field, 8) %}
+ val {% call kt::field_name(field, loop.index) %}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
+ {%- endfor -%}
) : {{ type_name }}() {
companion object
}
@@ -83,9 +99,9 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
is {{ type_name }}.{{ variant|type_name(ci) }} -> {
// Add the size for the Int that specifies the variant plus the size needed for all fields
(
- 4
+ 4UL
{%- for field in variant.fields() %}
- + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ + {{ field|allocation_size_fn }}(value.{%- call kt::field_name(field, loop.index) -%})
{%- endfor %}
)
}
@@ -98,7 +114,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
is {{ type_name }}.{{ variant|type_name(ci) }} -> {
buf.putInt({{ loop.index }})
{%- for field in variant.fields() %}
- {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {{ field|write_fn }}(value.{%- call kt::field_name(field, loop.index) -%}, buf)
{%- endfor %}
Unit
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
index 986db5424d..4760c03fd6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
@@ -3,24 +3,26 @@
{%- let canonical_type_name = type_|canonical_name %}
{% if e.is_flat() %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} {
- // Each variant is a nested class
- // Flat enums carries a string error message, so no special implementation is necessary.
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message)
{% endfor %}
- companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> {
override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf)
}
}
{%- else %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} {
- // Each variant is a nested class
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{%- let variant_name = variant|error_variant_name %}
class {{ variant_name }}(
{% for field in variant.fields() -%}
+ {%- call kt::docstring(field, 8) %}
val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
{% endfor -%}
) : {{ type_name }}() {
@@ -29,7 +31,7 @@ sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Di
}
{% endfor %}
- companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> {
override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf)
}
@@ -76,15 +78,15 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
{%- endif %}
}
- override fun allocationSize(value: {{ type_name }}): Int {
+ override fun allocationSize(value: {{ type_name }}): ULong {
{%- if e.is_flat() %}
- return 4
+ return 4UL
{%- else %}
return when(value) {
{%- for variant in e.variants() %}
is {{ type_name }}.{{ variant|error_variant_name }} -> (
// Add the size for the Int that specifies the variant plus the size needed for all fields
- 4
+ 4UL
{%- for field in variant.fields() %}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
index 0fade7a0bc..b7e77f0b2d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
@@ -1,5 +1,5 @@
{%- let package_name=self.external_type_package_name(module_path, namespace) %}
-{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %}
+{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name|class_name(ci)) %}
{%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %}
{%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %}
{%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
index 3b2c9d225a..0de90b9c4b 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
@@ -20,7 +20,7 @@ public interface FfiConverter<KotlinType, FfiType> {
// encoding, so we pessimistically allocate the largest size possible (3
// bytes per codepoint). Allocating extra bytes is not really a big deal
// because the `RustBuffer` is short-lived.
- fun allocationSize(value: KotlinType): Int
+ fun allocationSize(value: KotlinType): ULong
// Write a Kotlin type to a `ByteBuffer`
fun write(value: KotlinType, buf: ByteBuffer)
@@ -34,11 +34,11 @@ public interface FfiConverter<KotlinType, FfiType> {
fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
val rbuf = RustBuffer.alloc(allocationSize(value))
try {
- val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also {
+ val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity).also {
it.order(ByteOrder.BIG_ENDIAN)
}
write(value, bbuf)
- rbuf.writeField("len", bbuf.position())
+ rbuf.writeField("len", bbuf.position().toLong())
return rbuf
} catch (e: Throwable) {
RustBuffer.free(rbuf)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
index eafec5d122..be91ac8fcb 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterFloat: FfiConverter<Float, Float> {
return value
}
- override fun allocationSize(value: Float) = 4
+ override fun allocationSize(value: Float) = 4UL
override fun write(value: Float, buf: ByteBuffer) {
buf.putFloat(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
index 9fc2892c95..5eb465f0df 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterDouble: FfiConverter<Double, Double> {
return value
}
- override fun allocationSize(value: Double) = 8
+ override fun allocationSize(value: Double) = 8UL
override fun write(value: Double, buf: ByteBuffer) {
buf.putDouble(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
deleted file mode 100644
index 3544b2f9e6..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-{{ self.add_import("kotlinx.coroutines.CoroutineScope") }}
-{{ self.add_import("kotlinx.coroutines.delay") }}
-{{ self.add_import("kotlinx.coroutines.isActive") }}
-{{ self.add_import("kotlinx.coroutines.launch") }}
-
-internal const val UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0.toByte()
-internal const val UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED = 1.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2.toByte()
-
-// Callback function to execute a Rust task. The Kotlin code schedules these in a coroutine then
-// invokes them.
-internal interface UniFfiRustTaskCallback : com.sun.jna.Callback {
- fun callback(rustTaskData: Pointer?, statusCode: Byte)
-}
-
-internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback {
- fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte {
- if (rustTask == null) {
- FfiConverterForeignExecutor.drop(handle)
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- } else {
- val coroutineScope = FfiConverterForeignExecutor.lift(handle)
- if (coroutineScope.isActive) {
- val job = coroutineScope.launch {
- if (delayMs > 0) {
- delay(delayMs.toLong())
- }
- rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS)
- }
- job.invokeOnCompletion { cause ->
- if (cause != null) {
- rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_CANCELLED)
- }
- }
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- } else {
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED
- }
- }
- }
-}
-
-public object FfiConverterForeignExecutor: FfiConverter<CoroutineScope, USize> {
- internal val handleMap = UniFfiHandleMap<CoroutineScope>()
-
- internal fun drop(handle: USize) {
- handleMap.remove(handle)
- }
-
- internal fun register(lib: _UniFFILib) {
- {%- match ci.ffi_foreign_executor_callback_set() %}
- {%- when Some with (fn) %}
- lib.{{ fn.name() }}(UniFfiForeignExecutorCallback)
- {%- when None %}
- {#- No foreign executor, we don't set anything #}
- {% endmatch %}
- }
-
- // Number of live handles, exposed so we can test the memory management
- public fun handleCount() : Int {
- return handleMap.size
- }
-
- override fun allocationSize(value: CoroutineScope) = USize.size
-
- override fun lift(value: USize): CoroutineScope {
- return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift")
- }
-
- override fun read(buf: ByteBuffer): CoroutineScope {
- return lift(USize.readFromBuffer(buf))
- }
-
- override fun lower(value: CoroutineScope): USize {
- return handleMap.insert(value)
- }
-
- override fun write(value: CoroutineScope, buf: ByteBuffer) {
- lower(value).writeToBuffer(buf)
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt
new file mode 100644
index 0000000000..3a56648190
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt
@@ -0,0 +1,27 @@
+// Map handles to objects
+//
+// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code.
+internal class UniffiHandleMap<T: Any> {
+ private val map = ConcurrentHashMap<Long, T>()
+ private val counter = java.util.concurrent.atomic.AtomicLong(0)
+
+ val size: Int
+ get() = map.size
+
+ // Insert a new object into the handle map and get a handle for it
+ fun insert(obj: T): Long {
+ val handle = counter.getAndAdd(1)
+ map.put(handle, obj)
+ return handle
+ }
+
+ // Get an object from the handle map
+ fun get(handle: Long): T {
+ return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle")
+ }
+
+ // Remove an entry from the handlemap and get the Kotlin object back
+ fun remove(handle: Long): T {
+ return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle")
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
index 382a5f7413..1fdbd3ffc0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
@@ -1,30 +1,43 @@
// A handful of classes and functions to support the generated data structures.
// This would be a good candidate for isolating in its own ffi-support lib.
-// Error runtime.
+
+internal const val UNIFFI_CALL_SUCCESS = 0.toByte()
+internal const val UNIFFI_CALL_ERROR = 1.toByte()
+internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte()
+
@Structure.FieldOrder("code", "error_buf")
-internal open class RustCallStatus : Structure() {
+internal open class UniffiRustCallStatus : Structure() {
@JvmField var code: Byte = 0
@JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
- class ByValue: RustCallStatus(), Structure.ByValue
+ class ByValue: UniffiRustCallStatus(), Structure.ByValue
fun isSuccess(): Boolean {
- return code == 0.toByte()
+ return code == UNIFFI_CALL_SUCCESS
}
fun isError(): Boolean {
- return code == 1.toByte()
+ return code == UNIFFI_CALL_ERROR
}
fun isPanic(): Boolean {
- return code == 2.toByte()
+ return code == UNIFFI_CALL_UNEXPECTED_ERROR
+ }
+
+ companion object {
+ fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue {
+ val callStatus = UniffiRustCallStatus.ByValue()
+ callStatus.code = code
+ callStatus.error_buf = errorBuf
+ return callStatus
+ }
}
}
class InternalException(message: String) : Exception(message)
// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
-interface CallStatusErrorHandler<E> {
+interface UniffiRustCallStatusErrorHandler<E> {
fun lift(error_buf: RustBuffer.ByValue): E;
}
@@ -33,15 +46,15 @@ interface CallStatusErrorHandler<E> {
// synchronize itself
// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
-private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
- var status = RustCallStatus();
+private inline fun <U, E: Exception> uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler<E>, callback: (UniffiRustCallStatus) -> U): U {
+ var status = UniffiRustCallStatus();
val return_value = callback(status)
- checkCallStatus(errorHandler, status)
+ uniffiCheckCallStatus(errorHandler, status)
return return_value
}
-// Check RustCallStatus and throw an error if the call wasn't successful
-private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E>, status: RustCallStatus) {
+// Check UniffiRustCallStatus and throw an error if the call wasn't successful
+private fun<E: Exception> uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler<E>, status: UniffiRustCallStatus) {
if (status.isSuccess()) {
return
} else if (status.isError()) {
@@ -60,8 +73,8 @@ private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E
}
}
-// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
-object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
+// UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
+object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler<InternalException> {
override fun lift(error_buf: RustBuffer.ByValue): InternalException {
RustBuffer.free(error_buf)
return InternalException("Unexpected CALL_ERROR")
@@ -69,93 +82,38 @@ object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
}
// Call a rust function that returns a plain value
-private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
- return rustCallWithError(NullCallStatusErrorHandler, callback);
+private inline fun <U> uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U {
+ return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback);
}
-// IntegerType that matches Rust's `usize` / C's `size_t`
-public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) {
- // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin.
- override fun toByte() = toInt().toByte()
- // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed.
- @Deprecated("`toInt().toChar()` is deprecated")
- override fun toChar() = toInt().toChar()
- override fun toShort() = toInt().toShort()
-
- fun writeToBuffer(buf: ByteBuffer) {
- // Make sure we always write usize integers using native byte-order, since they may be
- // casted to pointer values
- buf.order(ByteOrder.nativeOrder())
- try {
- when (Native.SIZE_T_SIZE) {
- 4 -> buf.putInt(toInt())
- 8 -> buf.putLong(toLong())
- else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
- }
- } finally {
- buf.order(ByteOrder.BIG_ENDIAN)
- }
- }
-
- companion object {
- val size: Int
- get() = Native.SIZE_T_SIZE
-
- fun readFromBuffer(buf: ByteBuffer) : USize {
- // Make sure we always read usize integers using native byte-order, since they may be
- // casted from pointer values
- buf.order(ByteOrder.nativeOrder())
- try {
- return when (Native.SIZE_T_SIZE) {
- 4 -> USize(buf.getInt().toLong())
- 8 -> USize(buf.getLong())
- else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
- }
- } finally {
- buf.order(ByteOrder.BIG_ENDIAN)
- }
- }
+internal inline fun<T> uniffiTraitInterfaceCall(
+ callStatus: UniffiRustCallStatus,
+ makeCall: () -> T,
+ writeReturn: (T) -> Unit,
+) {
+ try {
+ writeReturn(makeCall())
+ } catch(e: Exception) {
+ callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR
+ callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString())
}
}
-
-// Map handles to objects
-//
-// This is used when the Rust code expects an opaque pointer to represent some foreign object.
-// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an
-// object reference , nor does it support leaking a reference to Rust.
-//
-// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to
-// Rust when it needs an opaque pointer.
-//
-// TODO: refactor callbacks to use this class
-internal class UniFfiHandleMap<T: Any> {
- private val map = ConcurrentHashMap<USize, T>()
- // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible
- // values seems like enough. If somehow we generate 4 billion handles, then this will wrap
- // around back to zero and we can assume the first handle generated will have been dropped by
- // then.
- private val counter = java.util.concurrent.atomic.AtomicInteger(0)
-
- val size: Int
- get() = map.size
-
- fun insert(obj: T): USize {
- val handle = USize(counter.getAndAdd(1).toLong())
- map.put(handle, obj)
- return handle
- }
-
- fun get(handle: USize): T? {
- return map.get(handle)
- }
-
- fun remove(handle: USize): T? {
- return map.remove(handle)
+internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallWithError(
+ callStatus: UniffiRustCallStatus,
+ makeCall: () -> T,
+ writeReturn: (T) -> Unit,
+ lowerError: (E) -> RustBuffer.ByValue
+) {
+ try {
+ writeReturn(makeCall())
+ } catch(e: Exception) {
+ if (e is E) {
+ callStatus.code = UNIFFI_CALL_ERROR
+ callStatus.error_buf = lowerError(e)
+ } else {
+ callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR
+ callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString())
+ }
}
}
-
-// FFI type for Rust future continuations
-internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback {
- fun callback(continuationHandle: USize, pollResult: Short);
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
index 75564276be..de8296fff6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterShort: FfiConverter<Short, Short> {
return value
}
- override fun allocationSize(value: Short) = 2
+ override fun allocationSize(value: Short) = 2UL
override fun write(value: Short, buf: ByteBuffer) {
buf.putShort(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
index b7a8131c8b..171809a9c4 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterInt: FfiConverter<Int, Int> {
return value
}
- override fun allocationSize(value: Int) = 4
+ override fun allocationSize(value: Int) = 4UL
override fun write(value: Int, buf: ByteBuffer) {
buf.putInt(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
index 601cfc7c2c..35cf8f3169 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterLong: FfiConverter<Long, Long> {
return value
}
- override fun allocationSize(value: Long) = 8
+ override fun allocationSize(value: Long) = 8UL
override fun write(value: Long, buf: ByteBuffer) {
buf.putLong(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
index 9237768dbf..27c98a6659 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterByte: FfiConverter<Byte, Byte> {
return value
}
- override fun allocationSize(value: Byte) = 1
+ override fun allocationSize(value: Byte) = 1UL
override fun write(value: Byte, buf: ByteBuffer) {
buf.put(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt
new file mode 100644
index 0000000000..0b4249fb11
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt
@@ -0,0 +1,14 @@
+{%- call kt::docstring_value(interface_docstring, 0) %}
+public interface {{ interface_name }} {
+ {% for meth in methods.iter() -%}
+ {%- call kt::docstring(meth, 4) %}
+ {% if meth.is_async() -%}suspend {% endif -%}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list(meth, true) %})
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+ companion object
+}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
index 776c402727..a80418eb00 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
@@ -12,8 +12,8 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_n
}
}
- override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int {
- val spaceForMapSize = 4
+ override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): ULong {
+ val spaceForMapSize = 4UL
val spaceForChildren = value.map { (k, v) ->
{{ key_type|allocation_size_fn }}(k) +
{{ value_type|allocation_size_fn }}(v)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
index 6a3aeada35..1bac8a435c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
@@ -13,14 +13,57 @@ private inline fun <reified Lib : Library> loadIndirect(
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
}
+// Define FFI callback types
+{%- for def in ci.ffi_definitions() %}
+{%- match def %}
+{%- when FfiDefinition::CallbackFunction(callback) %}
+internal interface {{ callback.name()|ffi_callback_name }} : com.sun.jna.Callback {
+ fun callback(
+ {%- for arg in callback.arguments() -%}
+ {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }},
+ {%- endfor -%}
+ {%- if callback.has_rust_call_status_arg() -%}
+ uniffiCallStatus: UniffiRustCallStatus,
+ {%- endif -%}
+ )
+ {%- match callback.return_type() %}
+ {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }}
+ {%- when None %}
+ {%- endmatch %}
+}
+{%- when FfiDefinition::Struct(ffi_struct) %}
+@Structure.FieldOrder({% for field in ffi_struct.fields() %}"{{ field.name()|var_name_raw }}"{% if !loop.last %}, {% endif %}{% endfor %})
+internal open class {{ ffi_struct.name()|ffi_struct_name }}(
+ {%- for field in ffi_struct.fields() %}
+ @JvmField internal var {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }},
+ {%- endfor %}
+) : Structure() {
+ class UniffiByValue(
+ {%- for field in ffi_struct.fields() %}
+ {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }},
+ {%- endfor %}
+ ): {{ ffi_struct.name()|ffi_struct_name }}({%- for field in ffi_struct.fields() %}{{ field.name()|var_name }}, {%- endfor %}), Structure.ByValue
+
+ internal fun uniffiSetValue(other: {{ ffi_struct.name()|ffi_struct_name }}) {
+ {%- for field in ffi_struct.fields() %}
+ {{ field.name()|var_name }} = other.{{ field.name()|var_name }}
+ {%- endfor %}
+ }
+
+}
+{%- when FfiDefinition::Function(_) %}
+{# functions are handled below #}
+{%- endmatch %}
+{%- endfor %}
+
// A JNA Library to expose the extern-C FFI definitions.
// This is an implementation detail which will be called internally by the public API.
-internal interface _UniFFILib : Library {
+internal interface UniffiLib : Library {
companion object {
- internal val INSTANCE: _UniFFILib by lazy {
- loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}")
- .also { lib: _UniFFILib ->
+ internal val INSTANCE: UniffiLib by lazy {
+ loadIndirect<UniffiLib>(componentName = "{{ ci.namespace() }}")
+ .also { lib: UniffiLib ->
uniffiCheckContractApiVersion(lib)
uniffiCheckApiChecksums(lib)
{% for fn in self.initialization_fns() -%}
@@ -28,6 +71,12 @@ internal interface _UniFFILib : Library {
{% endfor -%}
}
}
+ {% if ci.contains_object_types() %}
+ // The Cleaner for the whole library
+ internal val CLEANER: UniffiCleaner by lazy {
+ UniffiCleaner.create()
+ }
+ {%- endif %}
}
{% for func in ci.iter_ffi_function_definitions() -%}
@@ -37,7 +86,7 @@ internal interface _UniFFILib : Library {
{% endfor %}
}
-private fun uniffiCheckContractApiVersion(lib: _UniFFILib) {
+private fun uniffiCheckContractApiVersion(lib: UniffiLib) {
// Get the bindings contract version from our ComponentInterface
val bindings_contract_version = {{ ci.uniffi_contract_version() }}
// Get the scaffolding contract version by calling the into the dylib
@@ -48,7 +97,7 @@ private fun uniffiCheckContractApiVersion(lib: _UniFFILib) {
}
@Suppress("UNUSED_PARAMETER")
-private fun uniffiCheckApiChecksums(lib: _UniFFILib) {
+private fun uniffiCheckApiChecksums(lib: UniffiLib) {
{%- for (name, expected_checksum) in ci.iter_checksums() %}
if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt
new file mode 100644
index 0000000000..e3e85544d7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt
@@ -0,0 +1,40 @@
+
+// The cleaner interface for Object finalization code to run.
+// This is the entry point to any implementation that we're using.
+//
+// The cleaner registers objects and returns cleanables, so now we are
+// defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the
+// different implmentations available at compile time.
+interface UniffiCleaner {
+ interface Cleanable {
+ fun clean()
+ }
+
+ fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable
+
+ companion object
+}
+
+// The fallback Jna cleaner, which is available for both Android, and the JVM.
+private class UniffiJnaCleaner : UniffiCleaner {
+ private val cleaner = com.sun.jna.internal.Cleaner.getCleaner()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ UniffiJnaCleanable(cleaner.register(value, cleanUpTask))
+}
+
+private class UniffiJnaCleanable(
+ private val cleanable: com.sun.jna.internal.Cleaner.Cleanable,
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
+
+// We decide at uniffi binding generation time whether we were
+// using Android or not.
+// There are further runtime checks to chose the correct implementation
+// of the cleaner.
+{% if config.android_cleaner() %}
+{%- include "ObjectCleanerHelperAndroid.kt" %}
+{%- else %}
+{%- include "ObjectCleanerHelperJvm.kt" %}
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt
new file mode 100644
index 0000000000..d025879848
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt
@@ -0,0 +1,26 @@
+{{- self.add_import("android.os.Build") }}
+{{- self.add_import("androidx.annotation.RequiresApi") }}
+
+private fun UniffiCleaner.Companion.create(): UniffiCleaner =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ AndroidSystemCleaner()
+ } else {
+ UniffiJnaCleaner()
+ }
+
+// The SystemCleaner, available from API Level 33.
+// Some API Level 33 OSes do not support using it, so we require API Level 34.
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+private class AndroidSystemCleaner : UniffiCleaner {
+ val cleaner = android.system.SystemCleaner.cleaner()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ AndroidSystemCleanable(cleaner.register(value, cleanUpTask))
+}
+
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+private class AndroidSystemCleanable(
+ private val cleanable: java.lang.ref.Cleaner.Cleanable,
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt
new file mode 100644
index 0000000000..c43bc167fc
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt
@@ -0,0 +1,25 @@
+private fun UniffiCleaner.Companion.create(): UniffiCleaner =
+ try {
+ // For safety's sake: if the library hasn't been run in android_cleaner = true
+ // mode, but is being run on Android, then we still need to think about
+ // Android API versions.
+ // So we check if java.lang.ref.Cleaner is there, and use that…
+ java.lang.Class.forName("java.lang.ref.Cleaner")
+ JavaLangRefCleaner()
+ } catch (e: ClassNotFoundException) {
+ // … otherwise, fallback to the JNA cleaner.
+ UniffiJnaCleaner()
+ }
+
+private class JavaLangRefCleaner : UniffiCleaner {
+ val cleaner = java.lang.ref.Cleaner.create()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ JavaLangRefCleanable(cleaner.register(value, cleanUpTask))
+}
+
+private class JavaLangRefCleanable(
+ val cleanable: java.lang.ref.Cleaner.Cleanable
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
deleted file mode 100644
index b9352c690f..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-// Interface implemented by anything that can contain an object reference.
-//
-// Such types expose a `destroy()` method that must be called to cleanly
-// dispose of the contained objects. Failure to call this method may result
-// in memory leaks.
-//
-// The easiest way to ensure this method is called is to use the `.use`
-// helper method to execute a block and destroy the object at the end.
-interface Disposable {
- fun destroy()
- companion object {
- fun destroy(vararg args: Any?) {
- args.filterIsInstance<Disposable>()
- .forEach(Disposable::destroy)
- }
- }
-}
-
-inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
- try {
- block(this)
- } finally {
- try {
- // N.B. our implementation is on the nullable type `Disposable?`.
- this?.destroy()
- } catch (e: Throwable) {
- // swallow
- }
- }
-
-// The base class for all UniFFI Object types.
-//
-// This class provides core operations for working with the Rust `Arc<T>` pointer to
-// the live Rust struct on the other side of the FFI.
-//
-// There's some subtlety here, because we have to be careful not to operate on a Rust
-// struct after it has been dropped, and because we must expose a public API for freeing
-// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
-//
-// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
-// Method calls need to read this pointer from the object's state and pass it in to
-// the Rust FFI.
-//
-// * When an `FFIObject` is no longer needed, its pointer should be passed to a
-// special destructor function provided by the Rust FFI, which will drop the
-// underlying Rust struct.
-//
-// * Given an `FFIObject` instance, calling code is expected to call the special
-// `destroy` method in order to free it after use, either by calling it explicitly
-// or by using a higher-level helper like the `use` method. Failing to do so will
-// leak the underlying Rust struct.
-//
-// * We can't assume that calling code will do the right thing, and must be prepared
-// to handle Kotlin method calls executing concurrently with or even after a call to
-// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
-//
-// * We must never allow Rust code to operate on the underlying Rust struct after
-// the destructor has been called, and must never call the destructor more than once.
-// Doing so may trigger memory unsafety.
-//
-// If we try to implement this with mutual exclusion on access to the pointer, there is the
-// possibility of a race between a method call and a concurrent call to `destroy`:
-//
-// * Thread A starts a method call, reads the value of the pointer, but is interrupted
-// before it can pass the pointer over the FFI to Rust.
-// * Thread B calls `destroy` and frees the underlying Rust struct.
-// * Thread A resumes, passing the already-read pointer value to Rust and triggering
-// a use-after-free.
-//
-// One possible solution would be to use a `ReadWriteLock`, with each method call taking
-// a read lock (and thus allowed to run concurrently) and the special `destroy` method
-// taking a write lock (and thus blocking on live method calls). However, we aim not to
-// generate methods with any hidden blocking semantics, and a `destroy` method that might
-// block if called incorrectly seems to meet that bar.
-//
-// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track
-// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
-// has been called. These are updated according to the following rules:
-//
-// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
-// The initial value for the flag is false.
-//
-// * At the start of each method call, we atomically check the counter.
-// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
-// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
-//
-// * At the end of each method call, we atomically decrement and check the counter.
-// If it has reached zero then we destroy the underlying Rust struct.
-//
-// * When `destroy` is called, we atomically flip the flag from false to true.
-// If the flag was already true we silently fail.
-// Otherwise we atomically decrement and check the counter.
-// If it has reached zero then we destroy the underlying Rust struct.
-//
-// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
-// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
-//
-// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
-// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
-// of the underlying Rust code.
-//
-// In the future we may be able to replace some of this with automatic finalization logic, such as using
-// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
-// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
-// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
-// so there would still be some complexity here).
-//
-// Sigh...all of this for want of a robust finalization mechanism.
-//
-// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
-//
-abstract class FFIObject(
- protected val pointer: Pointer
-): Disposable, AutoCloseable {
-
- private val wasDestroyed = AtomicBoolean(false)
- private val callCounter = AtomicLong(1)
-
- open protected fun freeRustArcPtr() {
- // To be overridden in subclasses.
- }
-
- override fun destroy() {
- // Only allow a single call to this method.
- // TODO: maybe we should log a warning if called more than once?
- if (this.wasDestroyed.compareAndSet(false, true)) {
- // This decrement always matches the initial count of 1 given at creation time.
- if (this.callCounter.decrementAndGet() == 0L) {
- this.freeRustArcPtr()
- }
- }
- }
-
- @Synchronized
- override fun close() {
- this.destroy()
- }
-
- internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
- // Check and increment the call counter, to keep the object alive.
- // This needs a compare-and-set retry loop in case of concurrent updates.
- do {
- val c = this.callCounter.get()
- if (c == 0L) {
- throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
- }
- if (c == Long.MAX_VALUE) {
- throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
- }
- } while (! this.callCounter.compareAndSet(c, c + 1L))
- // Now we can safely do the method call without the pointer being freed concurrently.
- try {
- return block(this.pointer)
- } finally {
- // This decrement always matches the increment we performed above.
- if (this.callCounter.decrementAndGet() == 0L) {
- this.freeRustArcPtr()
- }
- }
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
index 8ce27a5d04..62cac7a4d0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
@@ -1,125 +1,282 @@
+// This template implements a class for working with a Rust struct via a Pointer/Arc<T>
+// to the live Rust struct on the other side of the FFI.
+//
+// Each instance implements core operations for working with the Rust `Arc<T>` and the
+// Kotlin Pointer to work with the live Rust struct on the other side of the FFI.
+//
+// There's some subtlety here, because we have to be careful not to operate on a Rust
+// struct after it has been dropped, and because we must expose a public API for freeing
+// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
+//
+// * Each instance holds an opaque pointer to the underlying Rust struct.
+// Method calls need to read this pointer from the object's state and pass it in to
+// the Rust FFI.
+//
+// * When an instance is no longer needed, its pointer should be passed to a
+// special destructor function provided by the Rust FFI, which will drop the
+// underlying Rust struct.
+//
+// * Given an instance, calling code is expected to call the special
+// `destroy` method in order to free it after use, either by calling it explicitly
+// or by using a higher-level helper like the `use` method. Failing to do so risks
+// leaking the underlying Rust struct.
+//
+// * We can't assume that calling code will do the right thing, and must be prepared
+// to handle Kotlin method calls executing concurrently with or even after a call to
+// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
+//
+// * We must never allow Rust code to operate on the underlying Rust struct after
+// the destructor has been called, and must never call the destructor more than once.
+// Doing so may trigger memory unsafety.
+//
+// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
+// is implemented to call the destructor when the Kotlin object becomes unreachable.
+// This is done in a background thread. This is not a panacea, and client code should be aware that
+// 1. the thread may starve if some there are objects that have poorly performing
+// `drop` methods or do significant work in their `drop` methods.
+// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
+// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
+//
+// If we try to implement this with mutual exclusion on access to the pointer, there is the
+// possibility of a race between a method call and a concurrent call to `destroy`:
+//
+// * Thread A starts a method call, reads the value of the pointer, but is interrupted
+// before it can pass the pointer over the FFI to Rust.
+// * Thread B calls `destroy` and frees the underlying Rust struct.
+// * Thread A resumes, passing the already-read pointer value to Rust and triggering
+// a use-after-free.
+//
+// One possible solution would be to use a `ReadWriteLock`, with each method call taking
+// a read lock (and thus allowed to run concurrently) and the special `destroy` method
+// taking a write lock (and thus blocking on live method calls). However, we aim not to
+// generate methods with any hidden blocking semantics, and a `destroy` method that might
+// block if called incorrectly seems to meet that bar.
+//
+// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
+// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
+// has been called. These are updated according to the following rules:
+//
+// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
+// The initial value for the flag is false.
+//
+// * At the start of each method call, we atomically check the counter.
+// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
+// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
+//
+// * At the end of each method call, we atomically decrement and check the counter.
+// If it has reached zero then we destroy the underlying Rust struct.
+//
+// * When `destroy` is called, we atomically flip the flag from false to true.
+// If the flag was already true we silently fail.
+// Otherwise we atomically decrement and check the counter.
+// If it has reached zero then we destroy the underlying Rust struct.
+//
+// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
+// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
+//
+// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
+// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
+// of the underlying Rust code.
+//
+// This makes a cleaner a better alternative to _not_ calling `destroy()` as
+// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
+// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
+// thread may be starved, and the app will leak memory.
+//
+// In this case, `destroy`ing manually may be a better solution.
+//
+// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
+// with Rust peers are reclaimed:
+//
+// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
+// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
+// 3. The memory is reclaimed when the process terminates.
+//
+// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
+//
+
+{{ self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+{%- if self.include_once_check("interface-support") %}
+ {%- include "ObjectCleanerHelper.kt" %}
+{%- endif %}
+
{%- let obj = ci|get_object_definition(name) %}
-{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %}
-{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
-{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+{%- let (interface_name, impl_class_name) = obj|object_names(ci) %}
+{%- let methods = obj.methods() %}
+{%- let interface_docstring = obj.docstring() %}
+{%- let is_error = ci.is_name_used_as_error(name) %}
+{%- let ffi_converter_name = obj|ffi_converter_name %}
-public interface {{ type_name }}Interface {
- {% for meth in obj.methods() -%}
- {%- match meth.throws_type() -%}
- {%- when Some with (throwable) -%}
- @Throws({{ throwable|type_name(ci) }}::class)
- {%- when None -%}
- {%- endmatch %}
- {% if meth.is_async() -%}
- suspend fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- else -%}
- fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- endif %}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
- {%- when None -%}
- {%- endmatch -%}
+{%- include "Interface.kt" %}
- {% endfor %}
- companion object
-}
+{%- call kt::docstring(obj, 0) %}
+{% if (is_error) %}
+open class {{ impl_class_name }} : Exception, Disposable, AutoCloseable, {{ interface_name }} {
+{% else -%}
+open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }} {
+{%- endif %}
-class {{ type_name }}(
- pointer: Pointer
-) : FFIObject(pointer), {{ type_name }}Interface {
+ constructor(pointer: Pointer) {
+ this.pointer = pointer
+ this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer))
+ }
+
+ /**
+ * This constructor can be used to instantiate a fake object. Only used for tests. Any
+ * attempt to actually use an object constructed this way will fail as there is no
+ * connected Rust object.
+ */
+ @Suppress("UNUSED_PARAMETER")
+ constructor(noPointer: NoPointer) {
+ this.pointer = null
+ this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer))
+ }
{%- match obj.primary_constructor() %}
- {%- when Some with (cons) %}
- constructor({% call kt::arg_list_decl(cons) -%}) :
+ {%- when Some(cons) %}
+ {%- if cons.is_async() %}
+ // Note no constructor generated for this object as it is async.
+ {%- else %}
+ {%- call kt::docstring(cons, 4) %}
+ constructor({% call kt::arg_list(cons, true) -%}) :
this({% call kt::to_ffi_call(cons) %})
+ {%- endif %}
{%- when None %}
{%- endmatch %}
- /**
- * Disconnect the object from the underlying Rust object.
- *
- * It can be called more than once, but once called, interacting with the object
- * causes an `IllegalStateException`.
- *
- * Clients **must** call this method once done with the object, or cause a memory leak.
- */
- override protected fun freeRustArcPtr() {
- rustCall() { status ->
- _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status)
+ protected val pointer: Pointer?
+ protected val cleanable: UniffiCleaner.Cleanable
+
+ private val wasDestroyed = AtomicBoolean(false)
+ private val callCounter = AtomicLong(1)
+
+ override fun destroy() {
+ // Only allow a single call to this method.
+ // TODO: maybe we should log a warning if called more than once?
+ if (this.wasDestroyed.compareAndSet(false, true)) {
+ // This decrement always matches the initial count of 1 given at creation time.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ cleanable.clean()
+ }
}
}
- {% for meth in obj.methods() -%}
- {%- match meth.throws_type() -%}
- {%- when Some with (throwable) %}
- @Throws({{ throwable|type_name(ci) }}::class)
- {%- else -%}
- {%- endmatch -%}
- {%- if meth.is_async() %}
- @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
- override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
- return uniffiRustCallAsync(
- callWithPointer { thisPtr ->
- _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}(
- thisPtr,
- {% call kt::arg_list_lowered(meth) %}
- )
- },
- {{ meth|async_poll(ci) }},
- {{ meth|async_complete(ci) }},
- {{ meth|async_free(ci) }},
- // lift function
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- { {{ return_type|lift_fn }}(it) },
- {%- when None %}
- { Unit },
- {% endmatch %}
- // Error FFI converter
- {%- match meth.throws_type() %}
- {%- when Some(e) %}
- {{ e|type_name(ci) }}.ErrorHandler,
- {%- when None %}
- NullCallStatusErrorHandler,
- {%- endmatch %}
- )
- }
- {%- else -%}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) -%}
- override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} =
- callWithPointer {
- {%- call kt::to_ffi_call_with_prefix("it", meth) %}
- }.let {
- {{ return_type|lift_fn }}(it)
+ @Synchronized
+ override fun close() {
+ this.destroy()
+ }
+
+ internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
+ // Check and increment the call counter, to keep the object alive.
+ // This needs a compare-and-set retry loop in case of concurrent updates.
+ do {
+ val c = this.callCounter.get()
+ if (c == 0L) {
+ throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
+ }
+ if (c == Long.MAX_VALUE) {
+ throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
+ }
+ } while (! this.callCounter.compareAndSet(c, c + 1L))
+ // Now we can safely do the method call without the pointer being freed concurrently.
+ try {
+ return block(this.uniffiClonePointer())
+ } finally {
+ // This decrement always matches the increment we performed above.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ cleanable.clean()
+ }
}
+ }
- {%- when None -%}
- override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) =
- callWithPointer {
- {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ // Use a static inner class instead of a closure so as not to accidentally
+ // capture `this` as part of the cleanable's action.
+ private class UniffiCleanAction(private val pointer: Pointer?) : Runnable {
+ override fun run() {
+ pointer?.let { ptr ->
+ uniffiRustCall { status ->
+ UniffiLib.INSTANCE.{{ obj.ffi_object_free().name() }}(ptr, status)
+ }
+ }
}
- {% endmatch %}
- {% endif %}
+ }
+
+ fun uniffiClonePointer(): Pointer {
+ return uniffiRustCall() { status ->
+ UniffiLib.INSTANCE.{{ obj.ffi_object_clone().name() }}(pointer!!, status)
+ }
+ }
+
+ {% for meth in obj.methods() -%}
+ {%- call kt::func_decl("override", meth, 4) %}
{% endfor %}
+ {%- for tm in obj.uniffi_traits() %}
+ {%- match tm %}
+ {% when UniffiTrait::Display { fmt } %}
+ override fun toString(): String {
+ return {{ fmt.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(fmt) %})
+ }
+ {% when UniffiTrait::Eq { eq, ne } %}
+ {# only equals used #}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is {{ impl_class_name}}) return false
+ return {{ eq.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(eq) %})
+ }
+ {% when UniffiTrait::Hash { hash } %}
+ override fun hashCode(): Int {
+ return {{ hash.return_type().unwrap()|lift_fn }}({%- call kt::to_ffi_call(hash) %}).toInt()
+ }
+ {%- else %}
+ {%- endmatch %}
+ {%- endfor %}
+
+ {# XXX - "companion object" confusion? How to have alternate constructors *and* be an error? #}
{% if !obj.alternate_constructors().is_empty() -%}
companion object {
{% for cons in obj.alternate_constructors() -%}
- fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ type_name }} =
- {{ type_name }}({% call kt::to_ffi_call(cons) %})
+ {% call kt::func_decl("", cons, 4) %}
{% endfor %}
}
+ {% else if is_error %}
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ impl_class_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ impl_class_name }} {
+ // Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer.
+ val bb = error_buf.asByteBuffer()
+ if (bb == null) {
+ throw InternalException("?")
+ }
+ return {{ ffi_converter_name }}.read(bb)
+ }
+ }
{% else %}
companion object
{% endif %}
}
-public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
- override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it }
+{%- if obj.has_callback_interface() %}
+{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %}
+{%- let vtable_methods = obj.vtable_methods() %}
+{%- let ffi_init_callback = obj.ffi_init_callback() %}
+{% include "CallbackInterfaceImpl.kt" %}
+{%- endif %}
+
+public object {{ ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
+ {%- if obj.has_callback_interface() %}
+ internal val handleMap = UniffiHandleMap<{{ type_name }}>()
+ {%- endif %}
+
+ override fun lower(value: {{ type_name }}): Pointer {
+ {%- if obj.has_callback_interface() %}
+ return Pointer(handleMap.insert(value))
+ {%- else %}
+ return value.uniffiClonePointer()
+ {%- endif %}
+ }
override fun lift(value: Pointer): {{ type_name }} {
- return {{ type_name }}(value)
+ return {{ impl_class_name }}(value)
}
override fun read(buf: ByteBuffer): {{ type_name }} {
@@ -128,7 +285,7 @@ public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointe
return lift(Pointer(buf.getLong()))
}
- override fun allocationSize(value: {{ type_name }}) = 8
+ override fun allocationSize(value: {{ type_name }}) = 8UL
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
// The Rust code always expects pointers written as 8 bytes,
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
index 56cb5f87a5..98451e1451 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
@@ -8,11 +8,11 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_nam
return {{ inner_type|read_fn }}(buf)
}
- override fun allocationSize(value: {{ inner_type_name }}?): Int {
+ override fun allocationSize(value: {{ inner_type_name }}?): ULong {
if (value == null) {
- return 1
+ return 1UL
} else {
- return 1 + {{ inner_type|allocation_size_fn }}(value)
+ return 1UL + {{ inner_type|allocation_size_fn }}(value)
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md
new file mode 100644
index 0000000000..0e770cb829
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md
@@ -0,0 +1,13 @@
+# Rules for the Kotlin template code
+
+## Naming
+
+Private variables, classes, functions, etc. should be prefixed with `uniffi`, `Uniffi`, or `UNIFFI`.
+This avoids naming collisions with user-defined items.
+Users will not get name collisions as long as they don't use "uniffi", which is reserved for us.
+
+In particular, make sure to use the `uniffi` prefix for any variable names in generated functions.
+If you name a variable something like `result` the code will probably work initially.
+Then it will break later on when a user decides to define a function with a parameter named `result`.
+
+Note: this doesn't apply to items that we want to expose, for example users may want to catch `InternalException` so doesn't get the `Uniffi` prefix.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
index b588ca1398..bc3028c736 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
@@ -1,8 +1,11 @@
{%- let rec = ci|get_record_definition(name) %}
+{%- if rec.has_fields() %}
+{%- call kt::docstring(rec, 0) %}
data class {{ type_name }} (
{%- for field in rec.fields() %}
- var {{ field.name()|var_name }}: {{ field|type_name(ci) -}}
+ {%- call kt::docstring(field, 4) %}
+ {% if config.generate_immutable_records() %}val{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name(ci) -}}
{%- match field.default_value() %}
{%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }}
{%- else %}
@@ -18,21 +21,39 @@ data class {{ type_name }} (
{% endif %}
companion object
}
+{%- else -%}
+{%- call kt::docstring(rec, 0) %}
+class {{ type_name }} {
+ override fun equals(other: Any?): Boolean {
+ return other is {{ type_name }}
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+
+ companion object
+}
+{%- endif %}
public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
override fun read(buf: ByteBuffer): {{ type_name }} {
+ {%- if rec.has_fields() %}
return {{ type_name }}(
{%- for field in rec.fields() %}
{{ field|read_fn }}(buf),
{%- endfor %}
)
+ {%- else %}
+ return {{ type_name }}()
+ {%- endif %}
}
- override fun allocationSize(value: {{ type_name }}) = (
+ override fun allocationSize(value: {{ type_name }}) = {%- if rec.has_fields() %} (
{%- for field in rec.fields() %}
- {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif %}
{%- endfor %}
- )
+ ) {%- else %} 0UL {%- endif %}
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
{%- for field in rec.fields() %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
index dfbea24074..b28f25bfc3 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
@@ -4,32 +4,41 @@
@Structure.FieldOrder("capacity", "len", "data")
open class RustBuffer : Structure() {
- @JvmField var capacity: Int = 0
- @JvmField var len: Int = 0
+ // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values.
+ // When dealing with these fields, make sure to call `toULong()`.
+ @JvmField var capacity: Long = 0
+ @JvmField var len: Long = 0
@JvmField var data: Pointer? = null
class ByValue: RustBuffer(), Structure.ByValue
class ByReference: RustBuffer(), Structure.ByReference
+ internal fun setValue(other: RustBuffer) {
+ capacity = other.capacity
+ len = other.len
+ data = other.data
+ }
+
companion object {
- internal fun alloc(size: Int = 0) = rustCall() { status ->
- _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status)
+ internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status ->
+ // Note: need to convert the size to a `Long` value to make this work with JVM.
+ UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toLong(), status)
}.also {
if(it.data == null) {
throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
}
}
- internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue {
+ internal fun create(capacity: ULong, len: ULong, data: Pointer?): RustBuffer.ByValue {
var buf = RustBuffer.ByValue()
- buf.capacity = capacity
- buf.len = len
+ buf.capacity = capacity.toLong()
+ buf.len = len.toLong()
buf.data = data
return buf
}
- internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
- _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
+ internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status ->
+ UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
}
}
@@ -53,9 +62,9 @@ class RustBufferByReference : ByReference(16) {
fun setValue(value: RustBuffer.ByValue) {
// NOTE: The offsets are as they are in the C-like struct.
val pointer = getPointer()
- pointer.setInt(0, value.capacity)
- pointer.setInt(4, value.len)
- pointer.setPointer(8, value.data)
+ pointer.setLong(0, value.capacity)
+ pointer.setLong(8, value.len)
+ pointer.setPointer(16, value.data)
}
/**
@@ -64,9 +73,9 @@ class RustBufferByReference : ByReference(16) {
fun getValue(): RustBuffer.ByValue {
val pointer = getPointer()
val value = RustBuffer.ByValue()
- value.writeField("capacity", pointer.getInt(0))
- value.writeField("len", pointer.getInt(4))
- value.writeField("data", pointer.getPointer(8))
+ value.writeField("capacity", pointer.getLong(0))
+ value.writeField("len", pointer.getLong(8))
+ value.writeField("data", pointer.getLong(16))
return value
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
index 876d1bc05e..61f911cb0c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
@@ -8,15 +8,15 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_typ
}
}
- override fun allocationSize(value: List<{{ inner_type_name }}>): Int {
- val sizeForLength = 4
+ override fun allocationSize(value: List<{{ inner_type_name }}>): ULong {
+ val sizeForLength = 4UL
val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum()
return sizeForLength + sizeForItems
}
override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) {
buf.putInt(value.size)
- value.forEach {
+ value.iterator().forEach {
{{ inner_type|write_fn }}(it, buf)
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
index 68324be4f9..b67435bd1a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
@@ -4,7 +4,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
// store our length and avoid writing it out to the buffer.
override fun lift(value: RustBuffer.ByValue): String {
try {
- val byteArr = ByteArray(value.len)
+ val byteArr = ByteArray(value.len.toInt())
value.asByteBuffer()!!.get(byteArr)
return byteArr.toString(Charsets.UTF_8)
} finally {
@@ -31,7 +31,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
val byteBuf = toUtf8(value)
// Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
// to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
- val rbuf = RustBuffer.alloc(byteBuf.limit())
+ val rbuf = RustBuffer.alloc(byteBuf.limit().toULong())
rbuf.asByteBuffer()!!.put(byteBuf)
return rbuf
}
@@ -39,9 +39,9 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
// We aren't sure exactly how many bytes our string will be once it's UTF-8
// encoded. Allocate 3 bytes per UTF-16 code unit which will always be
// enough.
- override fun allocationSize(value: String): Int {
- val sizeForLength = 4
- val sizeForString = value.length * 3
+ override fun allocationSize(value: String): ULong {
+ val sizeForLength = 4UL
+ val sizeForString = value.length.toULong() * 3UL
return sizeForLength + sizeForString
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
index 21069d7ce8..10a450a4bd 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
@@ -14,7 +14,7 @@ public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> {
}
// 8 bytes for seconds, 4 bytes for nanoseconds
- override fun allocationSize(value: java.time.Instant) = 12
+ override fun allocationSize(value: java.time.Instant) = 12UL
override fun write(value: java.time.Instant, buf: ByteBuffer) {
var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
index 6a841d3484..681c48093a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
@@ -1,51 +1 @@
-{%- if func.is_async() %}
-{%- match func.throws_type() -%}
-{%- when Some with (throwable) %}
-@Throws({{ throwable|type_name(ci) }}::class)
-{%- else -%}
-{%- endmatch %}
-
-@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
-suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
- return uniffiRustCallAsync(
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}),
- {{ func|async_poll(ci) }},
- {{ func|async_complete(ci) }},
- {{ func|async_free(ci) }},
- // lift function
- {%- match func.return_type() %}
- {%- when Some(return_type) %}
- { {{ return_type|lift_fn }}(it) },
- {%- when None %}
- { Unit },
- {% endmatch %}
- // Error FFI converter
- {%- match func.throws_type() %}
- {%- when Some(e) %}
- {{ e|type_name(ci) }}.ErrorHandler,
- {%- when None %}
- NullCallStatusErrorHandler,
- {%- endmatch %}
- )
-}
-
-{%- else %}
-{%- match func.throws_type() -%}
-{%- when Some with (throwable) %}
-@Throws({{ throwable|type_name(ci) }}::class)
-{%- else -%}
-{%- endmatch -%}
-
-{%- match func.return_type() -%}
-{%- when Some with (return_type) %}
-
-fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} {
- return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %})
-}
-{% when None %}
-
-fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) =
- {% call kt::to_ffi_call(func) %}
-
-{% endmatch %}
-{%- endif %}
+{%- call kt::func_decl("", func, 8) %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
index 103d444ea3..c27121b701 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
@@ -1,5 +1,38 @@
{%- import "macros.kt" as kt %}
+// Interface implemented by anything that can contain an object reference.
+//
+// Such types expose a `destroy()` method that must be called to cleanly
+// dispose of the contained objects. Failure to call this method may result
+// in memory leaks.
+//
+// The easiest way to ensure this method is called is to use the `.use`
+// helper method to execute a block and destroy the object at the end.
+interface Disposable {
+ fun destroy()
+ companion object {
+ fun destroy(vararg args: Any?) {
+ args.filterIsInstance<Disposable>()
+ .forEach(Disposable::destroy)
+ }
+ }
+}
+
+inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
+ try {
+ block(this)
+ } finally {
+ try {
+ // N.B. our implementation is on the nullable type `Disposable?`.
+ this?.destroy()
+ } catch (e: Throwable) {
+ // swallow
+ }
+ }
+
+/** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */
+object NoPointer
+
{%- for type_ in ci.iter_types() %}
{%- let type_name = type_|type_name(ci) %}
{%- let ffi_converter_name = type_|ffi_converter_name %}
@@ -82,9 +115,6 @@
{%- when Type::CallbackInterface { module_path, name } %}
{% include "CallbackInterfaceTemplate.kt" %}
-{%- when Type::ForeignExecutor %}
-{% include "ForeignExecutorTemplate.kt" %}
-
{%- when Type::Timestamp %}
{% include "TimestampHelper.kt" %}
@@ -104,6 +134,10 @@
{%- if ci.has_async_fns() %}
{# Import types needed for async support #}
{{ self.add_import("kotlin.coroutines.resume") }}
+{{ self.add_import("kotlinx.coroutines.launch") }}
{{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }}
{{ self.add_import("kotlinx.coroutines.CancellableContinuation") }}
+{{ self.add_import("kotlinx.coroutines.DelicateCoroutinesApi") }}
+{{ self.add_import("kotlinx.coroutines.Job") }}
+{{ self.add_import("kotlinx.coroutines.GlobalScope") }}
{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
index 279a8fa91b..b179145b62 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUShort: FfiConverter<UShort, Short> {
return value.toShort()
}
- override fun allocationSize(value: UShort) = 2
+ override fun allocationSize(value: UShort) = 2UL
override fun write(value: UShort, buf: ByteBuffer) {
buf.putShort(value.toShort())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
index da7b5b28d6..202d5bcd5b 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUInt: FfiConverter<UInt, Int> {
return value.toInt()
}
- override fun allocationSize(value: UInt) = 4
+ override fun allocationSize(value: UInt) = 4UL
override fun write(value: UInt, buf: ByteBuffer) {
buf.putInt(value.toInt())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
index 44d27ad36b..9be2a5a69d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterULong: FfiConverter<ULong, Long> {
return value.toLong()
}
- override fun allocationSize(value: ULong) = 8
+ override fun allocationSize(value: ULong) = 8UL
override fun write(value: ULong, buf: ByteBuffer) {
buf.putLong(value.toLong())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
index b6d176603e..ee360673e0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUByte: FfiConverter<UByte, Byte> {
return value.toByte()
}
- override fun allocationSize(value: UByte) = 1
+ override fun allocationSize(value: UByte) = 1UL
override fun write(value: UByte, buf: ByteBuffer) {
buf.put(value.toByte())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
index 6a95d6a66d..7acfdc8861 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
@@ -1,32 +1,91 @@
{#
// Template to call into rust. Used in several places.
-// Variable names in `arg_list_decl` should match up with arg lists
+// Variable names in `arg_list` should match up with arg lists
// passed to rust via `arg_list_lowered`
#}
{%- macro to_ffi_call(func) -%}
- {%- match func.throws_type() %}
- {%- when Some with (e) %}
- rustCallWithError({{ e|type_name(ci) }})
- {%- else %}
- rustCall()
- {%- endmatch %} { _status ->
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} _status)
-}
-{%- endmacro -%}
+ {%- if func.takes_self() %}
+ callWithPointer {
+ {%- call to_raw_ffi_call(func) %}
+ }
+ {% else %}
+ {%- call to_raw_ffi_call(func) %}
+ {% endif %}
+{%- endmacro %}
-{%- macro to_ffi_call_with_prefix(prefix, func) %}
+{%- macro to_raw_ffi_call(func) -%}
{%- match func.throws_type() %}
{%- when Some with (e) %}
- rustCallWithError({{ e|type_name(ci) }})
+ uniffiRustCallWithError({{ e|type_name(ci) }})
{%- else %}
- rustCall()
+ uniffiRustCall()
{%- endmatch %} { _status ->
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}(
- {{- prefix }},
- {% call arg_list_lowered(func) %}
+ UniffiLib.INSTANCE.{{ func.ffi_func().name() }}(
+ {% if func.takes_self() %}it, {% endif -%}
+ {% call arg_list_lowered(func) -%}
_status)
}
+{%- endmacro -%}
+
+{%- macro func_decl(func_decl, callable, indent) %}
+ {%- call docstring(callable, indent) %}
+ {%- match callable.throws_type() -%}
+ {%- when Some(throwable) %}
+ @Throws({{ throwable|type_name(ci) }}::class)
+ {%- else -%}
+ {%- endmatch -%}
+ {%- if callable.is_async() %}
+ @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+ {{ func_decl }} suspend fun {{ callable.name()|fn_name }}(
+ {%- call arg_list(callable, !callable.takes_self()) -%}
+ ){% match callable.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
+ return {% call call_async(callable) %}
+ }
+ {%- else -%}
+ {{ func_decl }} fun {{ callable.name()|fn_name }}(
+ {%- call arg_list(callable, !callable.takes_self()) -%}
+ ){%- match callable.return_type() -%}
+ {%- when Some with (return_type) -%}
+ : {{ return_type|type_name(ci) }} {
+ return {{ return_type|lift_fn }}({% call to_ffi_call(callable) %})
+ }
+ {%- when None %}
+ = {% call to_ffi_call(callable) %}
+ {%- endmatch %}
+ {% endif %}
+{% endmacro %}
+
+{%- macro call_async(callable) -%}
+ uniffiRustCallAsync(
+{%- if callable.takes_self() %}
+ callWithPointer { thisPtr ->
+ UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}(
+ thisPtr,
+ {% call arg_list_lowered(callable) %}
+ )
+ },
+{%- else %}
+ UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}({% call arg_list_lowered(callable) %}),
+{%- endif %}
+ {{ callable|async_poll(ci) }},
+ {{ callable|async_complete(ci) }},
+ {{ callable|async_free(ci) }},
+ // lift function
+ {%- match callable.return_type() %}
+ {%- when Some(return_type) %}
+ { {{ return_type|lift_fn }}(it) },
+ {%- when None %}
+ { Unit },
+ {% endmatch %}
+ // Error FFI converter
+ {%- match callable.throws_type() %}
+ {%- when Some(e) %}
+ {{ e|type_name(ci) }}.ErrorHandler,
+ {%- when None %}
+ UniffiNullRustCallStatusErrorHandler,
+ {%- endmatch %}
+ )
{%- endmacro %}
{%- macro arg_list_lowered(func) %}
@@ -37,37 +96,42 @@
{#-
// Arglist as used in kotlin declarations of methods, functions and constructors.
+// If is_decl, then default values be specified.
// Note the var_name and type_name filters.
-#}
-{% macro arg_list_decl(func) %}
- {%- for arg in func.arguments() -%}
+{% macro arg_list(func, is_decl) %}
+{%- for arg in func.arguments() -%}
{{ arg.name()|var_name }}: {{ arg|type_name(ci) }}
- {%- match arg.default_value() %}
- {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }}
- {%- else %}
- {%- endmatch %}
- {%- if !loop.last %}, {% endif -%}
- {%- endfor %}
+{%- if is_decl %}
+{%- match arg.default_value() %}
+{%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }}
+{%- else %}
+{%- endmatch %}
+{%- endif %}
+{%- if !loop.last %}, {% endif -%}
+{%- endfor %}
{%- endmacro %}
-{% macro arg_list_protocol(func) %}
- {%- for arg in func.arguments() -%}
- {{ arg.name()|var_name }}: {{ arg|type_name(ci) }}
- {%- if !loop.last %}, {% endif -%}
- {%- endfor %}
-{%- endmacro %}
{#-
-// Arglist as used in the _UniFFILib function declarations.
+// Arglist as used in the UniffiLib function declarations.
// Note unfiltered name but ffi_type_name filters.
-#}
{%- macro arg_list_ffi_decl(func) %}
{%- for arg in func.arguments() %}
{{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value -}},
{%- endfor %}
- {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %}
+ {%- if func.has_rust_call_status_arg() %}uniffi_out_err: UniffiRustCallStatus, {% endif %}
{%- endmacro -%}
+{% macro field_name(field, field_num) %}
+{%- if field.name().is_empty() -%}
+v{{- field_num -}}
+{%- else -%}
+{{ field.name()|var_name }}
+{%- endif -%}
+{%- endmacro %}
+
// Macro for destroying fields
{%- macro destroy_fields(member) %}
Disposable.destroy(
@@ -75,3 +139,15 @@
this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%}
{% endfor -%})
{%- endmacro -%}
+
+{%- macro docstring_value(maybe_docstring, indent_spaces) %}
+{%- match maybe_docstring %}
+{%- when Some(docstring) %}
+{{ docstring|docstring(indent_spaces) }}
+{%- else %}
+{%- endmatch %}
+{%- endmacro %}
+
+{%- macro docstring(defn, indent_spaces) %}
+{%- call docstring_value(defn.docstring(), indent_spaces) %}
+{%- endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
index 9ee4229018..2cdc72a5e2 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
@@ -1,6 +1,8 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
+{%- call kt::docstring_value(ci.namespace_docstring(), 0) %}
+
@file:Suppress("NAME_SHADOWING")
package {{ config.package_name() }};
@@ -28,6 +30,7 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.CharBuffer
import java.nio.charset.CodingErrorAction
+import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.ConcurrentHashMap
{%- for req in self.imports() %}
@@ -37,6 +40,7 @@ import java.util.concurrent.ConcurrentHashMap
{% include "RustBufferTemplate.kt" %}
{% include "FfiConverterTemplate.kt" %}
{% include "Helpers.kt" %}
+{% include "HandleMap.kt" %}
// Contains loading, initialization code,
// and the FFI Function declarations in a com.sun.jna.Library.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
index 7b78540741..0824015751 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
@@ -2,10 +2,8 @@
License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-use crate::{
- bindings::{RunScriptOptions, TargetLanguage},
- library_mode::generate_bindings,
-};
+use crate::bindings::TargetLanguage;
+use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault};
use anyhow::{bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use std::env;
@@ -33,14 +31,18 @@ pub fn run_script(
args: Vec<String>,
options: &RunScriptOptions,
) -> Result<()> {
- let script_path = Utf8Path::new(".").join(script_file);
+ let script_path = Utf8Path::new(script_file);
let test_helper = UniFFITestHelper::new(crate_name)?;
- let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
+ let out_dir = test_helper.create_out_dir(tmp_dir, script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
generate_bindings(
&cdylib_path,
None,
- &[TargetLanguage::Kotlin],
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Kotlin],
+ try_format_code: false,
+ },
+ None,
&out_dir,
false,
)?;