From 945d38fbcdb99471368d800cee882f57647d5ded Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Mon, 22 Jul 2024 15:23:24 -0400 Subject: [PATCH] Swift classes can inherit from traits Builds on #2196, #2204 and #2297. Closes #2169. --- .../tests/bindings/test_coverall.swift | 21 +++++++++++++++++++ .../src/bindings/swift/gen_swift/mod.rs | 21 +++++++++++++++++++ .../swift/templates/ObjectTemplate.swift | 6 +++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/fixtures/coverall/tests/bindings/test_coverall.swift b/fixtures/coverall/tests/bindings/test_coverall.swift index 084d4a589..a71687897 100644 --- a/fixtures/coverall/tests/bindings/test_coverall.swift +++ b/fixtures/coverall/tests/bindings/test_coverall.swift @@ -464,6 +464,27 @@ do { traits[0].setParent(parent: nil) } +// A struct which implements the node trait. +do { + let n = Node(name: "node") + assert(String(describing: n).starts(with: "Node { name: Some(\"node\"), parent: Mutex { ")) + assert(n.getParent()?.name() == "via node") + + n.setParent(parent: n.getParent()) + // doubly-wrapped :( + // Get: "Some(UniFFICallbackHandlerNodeTrait { handle: 19 })" + // Want: Like the Rust node above. + // debugPrint("parent \(n.describeParent())") + + let rustParent = Node(name: "parent") + n.setParent(parent: rustParent) + assert(n.getParent()?.name() == "parent") + + let swiftParent = SwiftNode() + rustParent.setParent(parent: swiftParent) + assert(ancestorNames(node: n) == ["parent", "node-swift"]) +} + // Test round tripping do { let rustGetters = makeRustGetters() diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 3b5f4404a..83b2ca6b1 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -272,6 +272,27 @@ impl Config { } } +// Given a trait, work out what the protocol name we generate for it. +// This differs based on whether the trait supports foreign impls (ie, +// whether is has a "callback interface". +fn trait_protocol_name(ci: &ComponentInterface, name: &str) -> Result { + let (obj_name, has_callback_interface) = match ci.get_object_definition(name) { + Some(obj) => (obj.name(), obj.has_callback_interface()), + None => ( + ci.get_callback_interface_definition(name) + .ok_or_else(|| anyhow::anyhow!("no interface {}", name))? + .name(), + true, + ), + }; + let class_name = SwiftCodeOracle.class_name(obj_name); + if has_callback_interface { + Ok(class_name) + } else { + Ok(format!("{class_name}Protocol")) + } +} + /// Generate UniFFI component bindings for Swift, as strings in memory. pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result { let header = BridgingHeader::new(config, ci) diff --git a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index d045fad45..d85ec23cf 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -25,7 +25,11 @@ open class {{ impl_class_name }}: {%- if is_error %} Swift.Error, {% endif %} - {{ protocol_name }} { + {%- for t in obj.trait_impls() %} + {{ self::trait_protocol_name(ci, t.trait_name)? }}, + {% endfor %} + {{ protocol_name }} + { fileprivate let pointer: UnsafeMutableRawPointer! /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly.