Skip to content

Commit 636fa27

Browse files
authored
Support Option<OpaqueSwiftType> (#272)
This adds support for bridging `Option<OpaqueSwiftType>` in `extern "Rust"` functions. This fixes #268, and makes the following now possible: ```rs #[swift_bridge::bridge] mod ffi { extern "Swift" { type SomeSwiftType; } extern "Rust" { fn option_arg(arg: Option<SomeSwiftType>); fn returns_option() -> Option<SomeSwiftType>; } } ```
1 parent c3c950c commit 636fa27

File tree

11 files changed

+392
-176
lines changed

11 files changed

+392
-176
lines changed

SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/Option.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ func swift_arg_option_str(arg: Optional<RustStr>) -> Bool {
6363
} else {
6464
return false
6565
}
66-
6766
}
6867

68+
public class OptTestOpaqueSwiftType {
69+
let val: Int
70+
71+
init(val: Int) {
72+
self.val = val
73+
}
74+
}

SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OptionTests.swift

+41-33
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,34 @@ class OptionTests: XCTestCase {
1515
func testSwiftCallRustOptionPrimitive() throws {
1616
XCTAssertEqual(rust_reflect_option_u8(70), 70)
1717
XCTAssertEqual(rust_reflect_option_u8(nil), nil)
18-
18+
1919
XCTAssertEqual(rust_reflect_option_i8(70), 70)
2020
XCTAssertEqual(rust_reflect_option_i8(nil), nil)
21-
21+
2222
XCTAssertEqual(rust_reflect_option_u16(70), 70)
2323
XCTAssertEqual(rust_reflect_option_u16(nil), nil)
24-
24+
2525
XCTAssertEqual(rust_reflect_option_i16(70), 70)
2626
XCTAssertEqual(rust_reflect_option_i16(nil), nil)
27-
27+
2828
XCTAssertEqual(rust_reflect_option_u32(70), 70)
2929
XCTAssertEqual(rust_reflect_option_u32(nil), nil)
30-
30+
3131
XCTAssertEqual(rust_reflect_option_i32(70), 70)
3232
XCTAssertEqual(rust_reflect_option_i32(nil), nil)
33-
33+
3434
XCTAssertEqual(rust_reflect_option_u64(70), 70)
3535
XCTAssertEqual(rust_reflect_option_u64(nil), nil)
36-
36+
3737
XCTAssertEqual(rust_reflect_option_i64(70), 70)
3838
XCTAssertEqual(rust_reflect_option_i64(nil), nil)
39-
39+
4040
XCTAssertEqual(rust_reflect_option_f32(70.0), 70.0)
4141
XCTAssertEqual(rust_reflect_option_f32(nil), nil)
42-
42+
4343
XCTAssertEqual(rust_reflect_option_f64(70.0), 70.0)
4444
XCTAssertEqual(rust_reflect_option_f64(nil), nil)
45-
45+
4646
XCTAssertEqual(rust_reflect_option_bool(true), true)
4747
XCTAssertEqual(rust_reflect_option_bool(false), false)
4848
XCTAssertEqual(rust_reflect_option_bool(nil), nil)
@@ -52,30 +52,30 @@ class OptionTests: XCTestCase {
5252
func testRustCallSwiftOptionPrimitive() throws {
5353
test_rust_calls_swift_option_primitive()
5454
}
55-
55+
5656
/// Verify that Swift can call a Rust function that accepts and returns an Option<T>
5757
/// where T is a String.
5858
func testSwiftCallRustReturnOptionString() throws {
5959
let string = rust_reflect_option_string("hello world")
6060
XCTAssertEqual(string!.toString(), "hello world")
61-
61+
6262
let none: String? = nil
6363
XCTAssertNil(rust_reflect_option_string(none))
6464
}
65-
65+
6666
/// We use an `Option<&'static str>` that we create on the Rust side so that
6767
/// we don't run into any lifetime issues.
6868
func testSwiftCallRustReturnOptionStr() throws {
6969
let str = rust_create_option_static_str()
7070
XCTAssertEqual(str!.toString(), "hello")
71-
71+
7272
let reflected = rust_reflect_option_str(str)
7373
XCTAssertEqual(reflected!.toString(), "hello")
74-
74+
7575
let none: RustStr? = nil
7676
XCTAssertNil(rust_reflect_option_str(none))
7777
}
78-
78+
7979
func testSwiftCallRustWithOptionVecOfPrimitiveType() throws {
8080
let vec = RustVec<UInt16>()
8181
vec.push(value: 123)
@@ -89,15 +89,24 @@ class OptionTests: XCTestCase {
8989

9090
XCTAssertNil(rust_reflect_option_vector_rust_type(nil))
9191
}
92-
92+
9393
func testSwiftCallRustWithOptionOpaqueRustType() throws {
9494
let val = OptTestOpaqueRustType(123)
9595
let reflect = rust_reflect_option_opaque_rust_type(val)
9696
XCTAssertEqual(reflect!.field(), 123)
97-
97+
9898
XCTAssertNil(rust_reflect_option_opaque_rust_type(nil))
9999
}
100100

101+
/// Verify that we can bridge options of opaque Swift types.
102+
func testSwiftCallRustWithOptionOpaqueSwiftType() throws {
103+
let val = OptTestOpaqueSwiftType(val: 727)
104+
let reflect = rust_reflect_option_opaque_swift_type(val)
105+
XCTAssertEqual(reflect!.val, 727)
106+
107+
XCTAssertNil(rust_reflect_option_opaque_swift_type(nil))
108+
}
109+
101110
/// Verify that we can pass and receive an `Option<&RustType>`.
102111
///
103112
/// We deinitialize the first reference and create a second to confirm that
@@ -110,39 +119,39 @@ class OptionTests: XCTestCase {
110119
XCTAssertEqual(reflect!.field(), 123)
111120
XCTAssertNil(rust_reflect_option_ref_opaque_rust_type(nil))
112121
reflect = nil
113-
122+
114123
reflect = rust_reflect_option_ref_opaque_rust_type(opt_ref)
115124
XCTAssertEqual(reflect!.field(), 123)
116125
}
117-
126+
118127
func testSwiftCallRustWithOptionOpaqueRustCopyType() throws {
119128
let val = new_opaque_rust_copy_type(123)
120129
let _: OptTestOpaqueRustCopyType? = rust_reflect_option_opaque_rust_copy_type(val)
121-
130+
122131
// TODO: Support methods on generic types
123132
// XCTAssertEqual(reflect!.field(), 123)
124133
XCTAssertNil(rust_reflect_option_opaque_rust_copy_type(nil))
125134
}
126-
135+
127136
func testSwiftCallRustWithOptionGenericOpaqueRustType() throws {
128137
let val = new_generic_opaque_rust_type(123)
129138
let _: OptTestGenericOpaqueRustType<UInt8>? = rust_reflect_option_generic_opaque_rust_type(val)
130-
139+
131140
// TODO: Support methods on generic types
132141
// XCTAssertEqual(reflect!.field(), 123)
133142
XCTAssertNil(rust_reflect_option_opaque_rust_type(nil))
134143
}
135-
144+
136145
func testSwiftCallRustWithOptionGenericOpaqueRustCopyType() throws {
137146
let val = new_generic_opaque_rust_copy_type(123)
138147
let _: OptTestGenericOpaqueRustCopyType? = rust_reflect_option_generic_opaque_rust_copy_type(val)
139-
148+
140149
// TODO: Support methods on generic types
141150
// XCTAssertEqual(reflect!.field(), 123)
142151
XCTAssertNil(rust_reflect_option_generic_opaque_rust_copy_type(nil))
143152
}
144153

145-
154+
146155
func testStructWithOptionFieldsSome() throws {
147156
let val = StructWithOptionFields(
148157
u8: 123, i8: 123, u16: 123, i16: 123,
@@ -165,7 +174,7 @@ class OptionTests: XCTestCase {
165174
XCTAssertEqual(reflected.f64, 123.4)
166175
XCTAssertEqual(reflected.boolean, true)
167176
}
168-
177+
169178
func testStructWithOptionFieldsNone() {
170179
let val = StructWithOptionFields(
171180
u8: nil, i8: nil, u16: nil, i16: nil,
@@ -187,29 +196,28 @@ class OptionTests: XCTestCase {
187196
XCTAssertEqual(reflected.f64, nil)
188197
XCTAssertEqual(reflected.boolean, nil)
189198
}
190-
199+
191200
func testEnumWhereVariantsHaveNoData() {
192201
let val = OptionEnumWithNoData.Variant2
193202
let reflectedSome = rust_reflect_option_enum_with_no_data(val)
194203
let reflectedNone = rust_reflect_option_enum_with_no_data(nil)
195-
204+
196205
switch reflectedSome! {
197206
case .Variant2:
198207
break;
199208
default:
200209
XCTFail()
201210
}
202-
211+
203212
XCTAssertNil(reflectedNone)
204213
}
205-
214+
206215
func testOptionStruct() {
207216
let val = OptionStruct(field: 123)
208217
let reflectedSome = rust_reflect_option_struct_with_no_data(val)
209218
let reflectedNone = rust_reflect_option_struct_with_no_data(nil)
210-
219+
211220
XCTAssertEqual(reflectedSome!.field, 123)
212221
XCTAssertNil(reflectedNone)
213222
}
214223
}
215-

crates/swift-bridge-ir/src/bridged_type.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1499,7 +1499,7 @@ impl BridgedType {
14991499
//
15001500
// Say we have an extern Rust function `create_string(str: &str) -> String`.
15011501
// It would be called using `__swift_bridge__$create_string(str)`
1502-
// But that would return a pointer to a swift_bridge::RustString.. So we need to convert that
1502+
// But that would return a pointer to a swift_bridge::RustString. So we need to convert that
15031503
// to something Swift can make use of.
15041504
// The final result on the Swift side would be:
15051505
//

crates/swift-bridge-ir/src/bridged_type/bridged_opaque_type.rs

+62-16
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,24 @@ impl BridgeableType for OpaqueForeignType {
311311
}
312312
}
313313
} else {
314-
quote! {
315-
if let Some(val) = #expression {
316-
Box::into_raw(Box::new(val))
317-
} else {
318-
std::ptr::null_mut()
314+
match self.host_lang {
315+
HostLang::Rust => {
316+
quote! {
317+
if let Some(val) = #expression {
318+
Box::into_raw(Box::new(val))
319+
} else {
320+
std::ptr::null_mut()
321+
}
322+
}
323+
}
324+
HostLang::Swift => {
325+
quote! {
326+
if let Some(val) = #expression {
327+
val.0.cast()
328+
} else {
329+
std::ptr::null_mut()
330+
}
331+
}
319332
}
320333
}
321334
}
@@ -414,7 +427,14 @@ impl BridgeableType for OpaqueForeignType {
414427
expression = expression,
415428
)
416429
} else {
417-
format!("{{ if let val = {expression} {{ val.isOwned = false; return val.ptr }} else {{ return nil }} }}()", expression = expression,)
430+
match self.host_lang {
431+
HostLang::Rust => {
432+
format!("{{ if let val = {expression} {{ val.isOwned = false; return val.ptr }} else {{ return nil }} }}()", expression = expression,)
433+
}
434+
HostLang::Swift => {
435+
format!("{{ if let val = {expression} {{ return Unmanaged.passRetained(val).retain().toOpaque() }} else {{ return nil }} }}()")
436+
}
437+
}
418438
}
419439
}
420440

@@ -479,11 +499,28 @@ impl BridgeableType for OpaqueForeignType {
479499
}
480500
}
481501
} else {
482-
quote! {
483-
if #expression.is_null() {
484-
None
485-
} else {
486-
Some(unsafe { * Box::from_raw(#expression) } )
502+
match self.host_lang {
503+
HostLang::Rust => {
504+
quote! {
505+
if #expression.is_null() {
506+
None
507+
} else {
508+
Some(unsafe { *Box::from_raw(#expression) } )
509+
}
510+
}
511+
}
512+
HostLang::Swift => {
513+
let ty = &self.ty;
514+
quote! {
515+
{
516+
let val = #expression;
517+
if val.is_null() {
518+
None
519+
} else {
520+
Some(#ty(val.cast()))
521+
}
522+
}
523+
}
487524
}
488525
}
489526
}
@@ -545,11 +582,20 @@ impl BridgeableType for OpaqueForeignType {
545582
)
546583
} else {
547584
let type_name = self.swift_name();
548-
format!(
549-
"{{ let val = {expression}; if val != nil {{ return {type_name}(ptr: val!) }} else {{ return nil }} }}()",
550-
expression = expression,
551-
type_name = type_name
552-
)
585+
match self.host_lang {
586+
HostLang::Rust => {
587+
format!(
588+
"{{ let val = {expression}; if val != nil {{ return {type_name}(ptr: val!) }} else {{ return nil }} }}()",
589+
expression = expression,
590+
type_name = type_name
591+
)
592+
}
593+
HostLang::Swift => {
594+
format!(
595+
"{{ if let val = {expression} {{ return Unmanaged<{type_name}>.fromOpaque(val).takeRetainedValue() }} else {{ return nil }} }}()"
596+
)
597+
}
598+
}
553599
}
554600
}
555601

crates/swift-bridge-ir/src/codegen/codegen_tests.rs

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ mod derive_struct_attribute_codegen_tests;
3939
mod extern_rust_function_opaque_rust_type_argument_codegen_tests;
4040
mod extern_rust_function_opaque_rust_type_return_codegen_tests;
4141
mod extern_rust_method_swift_class_placement_codegen_tests;
42-
mod extern_swift_function_opaque_swift_type_return_codegen_tests;
4342
mod function_attribute_codegen_tests;
4443
mod generic_opaque_rust_type_codegen_tests;
4544
mod opaque_rust_type_codegen_tests;

0 commit comments

Comments
 (0)