summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs')
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs251
1 files changed, 222 insertions, 29 deletions
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)))
+ }
}