Skip to content

Commit

Permalink
Kotlin classes can inherit from traits.
Browse files Browse the repository at this point in the history
Builds on mozilla#2204 which landed the metadata and Python support.
  • Loading branch information
mhammond committed Nov 1, 2024
1 parent 09bc9e9 commit af7b7c5
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 27 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@

- Fixed bug in metadata extraction with large ELF files.

### What's new?

- Kotlin: Proc-macros exporting an `impl Trait for Struct` block now has a class inheritance
hierarcy to reflect that. [#2297](https://github.com/mozilla/uniffi-rs/pull/2297)

[All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.28.2...HEAD).

### What's changed?
Expand Down
11 changes: 11 additions & 0 deletions fixtures/coverall/tests/bindings/test_coverall.kts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,17 @@ getTraits().let { traits ->
// not possible through the `NodeTrait` interface (see #1787).
}

// Structs which implement traits.
Node("node").let { n ->
assert(n.describeParent() == "Some(Node { name: Some(\"via node\"), parent: Mutex { data: None, poisoned: false, .. } })")
// NOTE same re-wrap problem described in the Python tests.
n.setParent(n.getParent())
// Expect: as above
// Get: `Some(UniFFICallbackHandlerNodeTrait { handle: 18 })`
// assert(n.describeParent() == "Some(Node { name: Some(\"via node\"), parent: Mutex { data: None, poisoned: false, .. } })")
n.setParent(Node("parent"))
}

makeRustGetters().let { rustGetters ->
// Check that these don't cause use-after-free bugs
testRoundTripThroughRust(rustGetters)
Expand Down
53 changes: 28 additions & 25 deletions uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,34 @@ impl<'a> KotlinWrapper<'a> {
}
}

/// Get the name of the interface and class name for a trait.
///
/// For a regular `struct Foo` or `trait Foo`, there's `FooInterface` with `Foo` as
/// the name of the (Rust implemented) object. But if it's a foreign trait:
/// * The name `Foo` is the name of the interface used by a the Kotlin implementation of the trait.
/// * The Rust implemented object is `FooImpl`.
///
/// This all impacts what types `FfiConverter.lower()` inputs. If it's a "foreign trait"
/// `lower` must lower anything that implements the interface (ie, a kotlin implementation).
/// If not, then lower only lowers the concrete class (ie, our simple instance with the pointer).
fn object_interface_name(ci: &ComponentInterface, obj: &Object) -> String {
let class_name = KotlinCodeOracle.class_name(ci, obj.name());
if obj.has_callback_interface() {
class_name
} else {
format!("{class_name}Interface")
}
}

fn object_impl_name(ci: &ComponentInterface, obj: &Object) -> String {
let class_name = KotlinCodeOracle.class_name(ci, obj.name());
if obj.has_callback_interface() {
format!("{class_name}Impl")
} else {
class_name
}
}

#[derive(Clone)]
pub struct KotlinCodeOracle;

Expand Down Expand Up @@ -443,24 +471,6 @@ impl KotlinCodeOracle {
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)
}
}
}

trait AsCodeType {
Expand Down Expand Up @@ -659,13 +669,6 @@ mod filters {
Ok(KotlinCodeOracle.ffi_struct_name(nm.as_ref()))
}

pub fn object_names(
obj: &Object,
ci: &ComponentInterface,
) -> Result<(String, String), rinja::Error> {
Ok(KotlinCodeOracle.object_names(ci, obj))
}

pub fn async_poll(
callable: impl Callable,
ci: &ComponentInterface,
Expand Down
10 changes: 8 additions & 2 deletions uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@
{%- endif %}

{%- let obj = ci|get_object_definition(name) %}
{%- let (interface_name, impl_class_name) = obj|object_names(ci) %}
{%- let interface_name = self::object_interface_name(ci, obj) %}
{%- let impl_class_name = self::object_impl_name(ci, obj) %}
{%- let methods = obj.methods() %}
{%- let interface_docstring = obj.docstring() %}
{%- let is_error = ci.is_name_used_as_error(name) %}
Expand All @@ -113,7 +114,12 @@
{% if (is_error) %}
open class {{ impl_class_name }} : kotlin.Exception, Disposable, AutoCloseable, {{ interface_name }} {
{% else -%}
open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }} {
open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }}
{%- for t in obj.trait_impls() %}
{%- let trait = ci|get_object_definition(t.trait_name) %}
, {{ self::object_interface_name(ci, trait) }}
{% endfor %}
{
{%- endif %}

constructor(pointer: Pointer) {
Expand Down

0 comments on commit af7b7c5

Please sign in to comment.