Skip to content

Commit

Permalink
More kotlin improvements: enum/error variants now work as a CodeType.
Browse files Browse the repository at this point in the history
  • Loading branch information
mhammond committed Jun 11, 2023
1 parent 80e0c33 commit aba9fec
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 168 deletions.
168 changes: 82 additions & 86 deletions uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod miscellany;
mod object;
mod primitives;
mod record;
mod variant;

// config options to customize the generated Kotlin.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -226,50 +227,8 @@ impl<'a> KotlinWrapper<'a> {
pub struct KotlinCodeOracle;

impl KotlinCodeOracle {
// Map `Type` instances to a `Box<dyn as_type>` for that type.
//
// There is a companion match in `templates/Types.kt` which performs a similar function for the
// template code.
//
// - When adding additional types here, make sure to also add a match arm to the `Types.kt` template.
// - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
fn create_code_type(&self, type_: Type) -> Box<dyn CodeType> {
match type_ {
Type::UInt8 => Box::new(primitives::UInt8CodeType),
Type::Int8 => Box::new(primitives::Int8CodeType),
Type::UInt16 => Box::new(primitives::UInt16CodeType),
Type::Int16 => Box::new(primitives::Int16CodeType),
Type::UInt32 => Box::new(primitives::UInt32CodeType),
Type::Int32 => Box::new(primitives::Int32CodeType),
Type::UInt64 => Box::new(primitives::UInt64CodeType),
Type::Int64 => Box::new(primitives::Int64CodeType),
Type::Float32 => Box::new(primitives::Float32CodeType),
Type::Float64 => Box::new(primitives::Float64CodeType),
Type::Boolean => Box::new(primitives::BooleanCodeType),
Type::String => Box::new(primitives::StringCodeType),
Type::Bytes => Box::new(primitives::BytesCodeType),

Type::Timestamp => Box::new(miscellany::TimestampCodeType),
Type::Duration => Box::new(miscellany::DurationCodeType),

Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
Type::CallbackInterface(id) => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
}
Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)),
Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)),
Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)),
Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
}
}

fn find(&self, type_: &Type) -> Box<dyn CodeType> {
self.create_code_type(type_.clone())
type_.clone().as_type().as_codetype()
}

/// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
Expand Down Expand Up @@ -334,58 +293,96 @@ impl KotlinCodeOracle {
}
}

pub mod filters {
use super::*;
pub trait AsCodeType {
fn as_codetype(&self) -> Box<dyn CodeType>;
}

fn oracle() -> &'static KotlinCodeOracle {
&KotlinCodeOracle
impl<T: AsType> AsCodeType for T {
fn as_codetype(&self) -> Box<dyn CodeType> {
// Map `Type` instances to a `Box<dyn CodeType>` for that type.
//
// There is a companion match in `templates/Types.kt` which performs a similar function for the
// template code.
//
// - When adding additional types here, make sure to also add a match arm to the `Types.kt` template.
// - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
match self.as_type() {
Type::UInt8 => Box::new(primitives::UInt8CodeType),
Type::Int8 => Box::new(primitives::Int8CodeType),
Type::UInt16 => Box::new(primitives::UInt16CodeType),
Type::Int16 => Box::new(primitives::Int16CodeType),
Type::UInt32 => Box::new(primitives::UInt32CodeType),
Type::Int32 => Box::new(primitives::Int32CodeType),
Type::UInt64 => Box::new(primitives::UInt64CodeType),
Type::Int64 => Box::new(primitives::Int64CodeType),
Type::Float32 => Box::new(primitives::Float32CodeType),
Type::Float64 => Box::new(primitives::Float64CodeType),
Type::Boolean => Box::new(primitives::BooleanCodeType),
Type::String => Box::new(primitives::StringCodeType),
Type::Bytes => Box::new(primitives::BytesCodeType),

Type::Timestamp => Box::new(miscellany::TimestampCodeType),
Type::Duration => Box::new(miscellany::DurationCodeType),

Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
Type::CallbackInterface(id) => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
}
Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)),
Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)),
Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)),
Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
}
}
}

pub mod filters {
use super::*;

pub fn type_name(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(oracle().find(&as_type.as_type()).type_label())
pub fn type_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(as_ct.as_codetype().type_label())
}

pub fn canonical_name(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(oracle().find(&as_type.as_type()).canonical_name())
pub fn canonical_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(as_ct.as_codetype().canonical_name())
}

pub fn ffi_converter_name(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(oracle().find(&as_type.as_type()).ffi_converter_name())
pub fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(as_ct.as_codetype().ffi_converter_name())
}

pub fn lower_fn(as_type: &impl AsType) -> Result<String, askama::Error> {
pub fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!(
"{}.lower",
oracle().find(&as_type.as_type()).ffi_converter_name()
as_ct.as_codetype().ffi_converter_name()
))
}

pub fn allocation_size_fn(as_type: &impl AsType) -> Result<String, askama::Error> {
pub fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!(
"{}.allocationSize",
oracle().find(&as_type.as_type()).ffi_converter_name()
as_ct.as_codetype().ffi_converter_name()
))
}

pub fn write_fn(as_type: &impl AsType) -> Result<String, askama::Error> {
pub fn write_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!(
"{}.write",
oracle().find(&as_type.as_type()).ffi_converter_name()
as_ct.as_codetype().ffi_converter_name()
))
}

pub fn lift_fn(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(format!(
"{}.lift",
oracle().find(&as_type.as_type()).ffi_converter_name()
))
pub fn lift_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name()))
}

pub fn read_fn(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(format!(
"{}.read",
oracle().find(&as_type.as_type()).ffi_converter_name()
))
pub fn read_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name()))
}

pub fn error_handler(result_type: &ResultType) -> Result<String, askama::Error> {
Expand Down Expand Up @@ -419,40 +416,39 @@ pub mod filters {

pub fn render_literal(
literal: &Literal,
as_type: &impl AsType,
as_ct: &impl AsCodeType,
) -> Result<String, askama::Error> {
Ok(oracle().find(&as_type.as_type()).literal(literal))
Ok(as_ct.as_codetype().literal(literal))
}

/// Get the Kotlin syntax for representing a given low-level `FfiType`.
pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
Ok(oracle().ffi_type_label(type_))
}

/// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
pub fn class_name(nm: &str) -> Result<String, askama::Error> {
Ok(oracle().class_name(nm))
Ok(KotlinCodeOracle.ffi_type_label(type_))
}

/// Get the idiomatic Kotlin rendering of a function name.
pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
Ok(oracle().fn_name(nm))
Ok(KotlinCodeOracle.fn_name(nm))
}

/// Get the idiomatic Kotlin rendering of a variable name.
pub fn var_name(nm: &str) -> Result<String, askama::Error> {
Ok(oracle().var_name(nm))
Ok(KotlinCodeOracle.var_name(nm))
}

/// Get the idiomatic Kotlin rendering of an individual enum variant.
pub fn enum_variant(nm: &str) -> Result<String, askama::Error> {
Ok(oracle().enum_variant_name(nm))
pub fn variant_name(v: &Variant) -> Result<String, askama::Error> {
Ok(KotlinCodeOracle.enum_variant_name(v.name()))
}

/// Get a codetype for idiomatic Kotlin rendering of an individual enum variant.
pub fn enum_variant(v: &Variant) -> Result<impl AsCodeType, askama::Error> {
Ok(v.clone())
}

/// Get the idiomatic Kotlin rendering of an exception name, replacing
/// `Error` with `Exception`.
pub fn exception_name(nm: &str) -> Result<String, askama::Error> {
Ok(oracle().error_name(nm))
/// Get a codetype for idiomatic Kotlin rendering of an individual enum variant
/// when used in an error.
pub fn error_variant(v: &Variant) -> Result<impl AsCodeType, askama::Error> {
Ok(variant::ErrorVariantCodeTypeProvider { v: v.clone() })
}

/// Remove the "`" chars we put around function/variable names
Expand Down
45 changes: 45 additions & 0 deletions uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::{AsCodeType, KotlinCodeOracle};
use crate::backend::CodeType;
use crate::interface::Variant;

#[derive(Debug)]
pub(super) struct VariantCodeType {
pub v: Variant,
}

impl CodeType for VariantCodeType {
fn type_label(&self) -> String {
KotlinCodeOracle.class_name(self.v.name())
}
}

impl AsCodeType for Variant {
fn as_codetype(&self) -> Box<dyn CodeType> {
Box::new(VariantCodeType { v: self.clone() })
}
}

#[derive(Debug)]
pub(super) struct ErrorVariantCodeType {
pub v: Variant,
}

impl CodeType for ErrorVariantCodeType {
fn type_label(&self) -> String {
KotlinCodeOracle.error_name(self.v.name())
}
}

pub(super) struct ErrorVariantCodeTypeProvider {
pub v: Variant,
}

impl AsCodeType for ErrorVariantCodeTypeProvider {
fn as_codetype(&self) -> Box<dyn CodeType> {
Box::new(ErrorVariantCodeType { v: self.v.clone() })
}
}
14 changes: 7 additions & 7 deletions uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

enum class {{ type_name }} {
{% for variant in e.variants() -%}
{{ variant.name()|enum_variant }}{% if loop.last %};{% else %},{% endif %}
{{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %}
{%- endfor %}
}

Expand All @@ -33,9 +33,9 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}
sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} {
{% for variant in e.variants() -%}
{% if !variant.has_fields() -%}
object {{ variant.name()|class_name }} : {{ type_name }}()
object {{ variant|enum_variant|type_name }} : {{ type_name }}()
{% else -%}
data class {{ variant.name()|class_name }}(
data class {{ variant|enum_variant|type_name }}(
{% for field in variant.fields() -%}
val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %}
{% endfor -%}
Expand All @@ -48,7 +48,7 @@ sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% e
override fun destroy() {
when(this) {
{%- for variant in e.variants() %}
is {{ type_name }}.{{ variant.name()|class_name }} -> {
is {{ type_name }}.{{ variant|enum_variant|type_name }} -> {
{%- if variant.has_fields() %}
{% call kt::destroy_fields(variant) %}
{% else -%}
Expand All @@ -65,7 +65,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
override fun read(buf: ByteBuffer): {{ type_name }} {
return when(buf.getInt()) {
{%- for variant in e.variants() %}
{{ loop.index }} -> {{ type_name }}.{{ variant.name()|class_name }}{% if variant.has_fields() %}(
{{ loop.index }} -> {{ type_name }}.{{ variant|enum_variant|type_name }}{% if variant.has_fields() %}(
{% for field in variant.fields() -%}
{{ field|read_fn }}(buf),
{% endfor -%}
Expand All @@ -77,7 +77,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }

override fun allocationSize(value: {{ type_name }}) = when(value) {
{%- for variant in e.variants() %}
is {{ type_name }}.{{ variant.name()|class_name }} -> {
is {{ type_name }}.{{ variant|enum_variant|type_name }} -> {
// Add the size for the Int that specifies the variant plus the size needed for all fields
(
4
Expand All @@ -92,7 +92,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
when(value) {
{%- for variant in e.variants() %}
is {{ type_name }}.{{ variant.name()|class_name }} -> {
is {{ type_name }}.{{ variant|enum_variant|type_name }} -> {
buf.putInt({{ loop.index }})
{%- for field in variant.fields() %}
{{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
Expand Down
Loading

0 comments on commit aba9fec

Please sign in to comment.