diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ae85fdab2..fc49796b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ - The `rust_future_continuation_callback_set` FFI function was removed. `rust_future_poll` now inputs the callback pointer. External bindings authors will need to update their code. +### What's new? + +- Rust traits `Display`, `Hash` and `Eq` exposed to Kotlin and Swift. + ## v0.25.0 (backend crates: v0.25.0) - (_2023-10-18_) [All changes in v0.25.0](https://github.com/mozilla/uniffi-rs/compare/v0.24.3...v0.25.0). diff --git a/fixtures/trait-methods/tests/bindings/test.kts b/fixtures/trait-methods/tests/bindings/test.kts new file mode 100644 index 0000000000..82357477e6 --- /dev/null +++ b/fixtures/trait-methods/tests/bindings/test.kts @@ -0,0 +1,11 @@ +import uniffi.trait_methods.* + +val m = TraitMethods("yo") +assert(m.toString() == "TraitMethods(yo)") + +assert(m == TraitMethods("yo")) +assert(m != TraitMethods("yoyo")) + +val map = mapOf(m to 1, TraitMethods("yoyo") to 2) +assert(map[m] == 1) +assert(map[TraitMethods("yoyo")] == 2) diff --git a/fixtures/trait-methods/tests/bindings/test.swift b/fixtures/trait-methods/tests/bindings/test.swift new file mode 100644 index 0000000000..c7a7adbc03 --- /dev/null +++ b/fixtures/trait-methods/tests/bindings/test.swift @@ -0,0 +1,12 @@ +import trait_methods + +let m = TraitMethods(name: "yo") +assert(String(describing: m) == "TraitMethods(yo)") +assert(String(reflecting: m) == "TraitMethods { val: \"yo\" }") + +// eq +assert(m == TraitMethods(name: "yo")) + +// hash +var set: Set = [TraitMethods(name: "yo")] +assert(set.contains(TraitMethods(name: "yo"))) diff --git a/fixtures/trait-methods/tests/test_generated_bindings.rs b/fixtures/trait-methods/tests/test_generated_bindings.rs index 20b487d74b..a6c3de1fd3 100644 --- a/fixtures/trait-methods/tests/test_generated_bindings.rs +++ b/fixtures/trait-methods/tests/test_generated_bindings.rs @@ -1 +1,5 @@ -uniffi::build_foreign_language_testcases!("tests/bindings/test.py",); +uniffi::build_foreign_language_testcases!( + "tests/bindings/test.py", + "tests/bindings/test.kts", + "tests/bindings/test.swift" +); diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt index cef6be881f..3ba3333da2 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -84,6 +84,37 @@ class {{ impl_class_name }}( {% endif %} {% endfor %} + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {%- when UniffiTrait::Display { fmt } %} + override fun toString(): String = + callWithPointer { + {%- call kt::to_ffi_call_with_prefix("it", fmt) %} + }.let { + {{ fmt.return_type().unwrap()|lift_fn }}(it) + } + {%- 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 callWithPointer { + {%- call kt::to_ffi_call_with_prefix("it", eq) %} + }.let { + {{ eq.return_type().unwrap()|lift_fn }}(it) + } + } + {%- when UniffiTrait::Hash { hash } %} + override fun hashCode(): Int = + callWithPointer { + {%- call kt::to_ffi_call_with_prefix("it", hash) %} + }.let { + {{ hash.return_type().unwrap()|lift_fn }}(it).toInt() + } + {%- else %} + {%- endmatch %} + {%- endfor %} + {% if !obj.alternate_constructors().is_empty() -%} companion object { {% for cons in obj.alternate_constructors() -%} diff --git a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index b63631f40e..e39fbeff97 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -4,7 +4,21 @@ {% include "Protocol.swift" %} -public class {{ impl_class_name }}: {{ protocol_name }} { +public class {{ impl_class_name }}: + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {%- when UniffiTrait::Display { fmt } %} + CustomStringConvertible, + {%- when UniffiTrait::Debug { fmt } %} + CustomDebugStringConvertible, + {%- when UniffiTrait::Eq { eq, ne } %} + Equatable, + {%- when UniffiTrait::Hash { hash } %} + Hashable, + {%- else %} + {%- endmatch %} + {%- endfor %} + {{ protocol_name }} { fileprivate let pointer: UnsafeMutableRawPointer // TODO: We'd like this to be `private` but for Swifty reasons, @@ -88,6 +102,38 @@ public class {{ impl_class_name }}: {{ protocol_name }} { {%- endmatch -%} {%- endif -%} {% endfor %} + + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {%- when UniffiTrait::Display { fmt } %} + public var description: String { + return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call_with_prefix("self.pointer", fmt) %} + ) + } + {%- when UniffiTrait::Debug { fmt } %} + public var debugDescription: String { + return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call_with_prefix("self.pointer", fmt) %} + ) + } + {%- when UniffiTrait::Eq { eq, ne } %} + public static func == (lhs: {{ impl_class_name }}, other: {{ impl_class_name }}) -> Bool { + return {% call swift::try(eq) %} {{ eq.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call_with_prefix("lhs.pointer", eq) %} + ) + } + {%- when UniffiTrait::Hash { hash } %} + public func hash(into hasher: inout Hasher) { + let val = {% call swift::try(hash) %} {{ hash.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call_with_prefix("self.pointer", hash) %} + ) + hasher.combine(val) + } + {%- else %} + {%- endmatch %} + {%- endfor %} + } {%- if obj.is_trait_interface() %}