Skip to content

Commit 53b118d

Browse files
authored
Add test cases for Option<&T> and fix rust codegen (#257)
Add test cases for Option<&T> and fix rust codegen Currently swift-bridge only has tests for Option<T> - this adds test cases for Option<&T> and fixes a bug in rust codegen that does not correctly translate Option<&T>. This is now possible: ```rust mod ffi { extern "Rust" { type SomeType; fn my_func(arg: Option<&SomeType>) -> Option<&SomeType>; } } ```
1 parent dd5bef5 commit 53b118d

File tree

4 files changed

+217
-11
lines changed

4 files changed

+217
-11
lines changed

SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OptionTests.swift

+17
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,23 @@ class OptionTests: XCTestCase {
9797

9898
XCTAssertNil(rust_reflect_option_opaque_rust_type(nil))
9999
}
100+
101+
/// Verify that we can pass and receive an `Option<&RustType>`.
102+
///
103+
/// We deinitialize the first reference and create a second to confirm that
104+
/// deinitializing the reference does not deinitialize the Rust type.
105+
func testSwiftCallRustWithOptionRefOpaqueRustType() throws {
106+
let val = OptTestOpaqueRefRustType.new(123)
107+
let opt_ref = val.field_ref()
108+
109+
var reflect = rust_reflect_option_ref_opaque_rust_type(opt_ref)
110+
XCTAssertEqual(reflect!.field(), 123)
111+
XCTAssertNil(rust_reflect_option_ref_opaque_rust_type(nil))
112+
reflect = nil
113+
114+
reflect = rust_reflect_option_ref_opaque_rust_type(opt_ref)
115+
XCTAssertEqual(reflect!.field(), 123)
116+
}
100117

101118
func testSwiftCallRustWithOptionOpaqueRustCopyType() throws {
102119
let val = new_opaque_rust_copy_type(123)

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

+50-10
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,12 @@ impl BridgeableType for OpaqueForeignType {
198198
let generics = self
199199
.generics
200200
.angle_bracketed_concrete_generics_tokens(types);
201-
quote! { *mut super::#type_name #generics }
201+
202+
if self.reference {
203+
quote! { *const super::#type_name #generics }
204+
} else {
205+
quote! { *mut super::#type_name #generics }
206+
}
202207
}
203208
}
204209

@@ -283,6 +288,16 @@ impl BridgeableType for OpaqueForeignType {
283288
}
284289
}
285290
}
291+
} else if self.reference {
292+
let ty = &self.ty;
293+
294+
quote! {
295+
if let Some(val) = #expression {
296+
val as *const super::#ty
297+
} else {
298+
std::ptr::null()
299+
}
300+
}
286301
} else {
287302
quote! {
288303
if let Some(val) = #expression {
@@ -312,15 +327,23 @@ impl BridgeableType for OpaqueForeignType {
312327
TypePosition::FnArg(func_host_lang, _)
313328
| TypePosition::FnReturn(func_host_lang) => {
314329
if func_host_lang.is_rust() {
315-
format!(
316-
"{{{}.isOwned = false; return {}.ptr;}}()",
317-
expression, expression
318-
)
330+
if self.reference {
331+
format!("{{return {}.ptr;}}()", expression)
332+
} else {
333+
format!(
334+
"{{{}.isOwned = false; return {}.ptr;}}()",
335+
expression, expression
336+
)
337+
}
319338
} else {
320-
format!(
321-
"{{{}.isOwned = false; return {}.ptr;}}()",
322-
expression, expression
323-
)
339+
if self.reference {
340+
format!("{{return {}.ptr;}}()", expression)
341+
} else {
342+
format!(
343+
"{{{}.isOwned = false; return {}.ptr;}}()",
344+
expression, expression
345+
)
346+
}
324347
}
325348
}
326349
TypePosition::SharedStructField => {
@@ -373,6 +396,11 @@ impl BridgeableType for OpaqueForeignType {
373396
option_ffi_repr = option_ffi_repr,
374397
ffi_repr = ffi_repr
375398
)
399+
} else if self.reference {
400+
format!(
401+
"{{ if let val = {expression} {{ return val.ptr }} else {{ return nil }} }}()",
402+
expression = expression,
403+
)
376404
} else {
377405
format!("{{ if let val = {expression} {{ val.isOwned = false; return val.ptr }} else {{ return nil }} }}()", expression = expression,)
378406
}
@@ -430,6 +458,14 @@ impl BridgeableType for OpaqueForeignType {
430458
None
431459
}
432460
}
461+
} else if self.reference {
462+
quote! {
463+
if #expression.is_null() {
464+
None
465+
} else {
466+
Some(unsafe {& * #expression} )
467+
}
468+
}
433469
} else {
434470
quote! {
435471
if #expression.is_null() {
@@ -638,7 +674,11 @@ impl BridgeableType for OpaqueForeignType {
638674

639675
impl OpaqueForeignType {
640676
pub fn swift_name(&self) -> String {
641-
format!("{}", self.ty)
677+
if self.reference {
678+
format!("{}Ref", self.ty)
679+
} else {
680+
format!("{}", self.ty)
681+
}
642682
}
643683

644684
/// The name of the type used to pass a `#[swift_bridge(Copy(...))]` type over FFI

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

+118
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,124 @@ void* __swift_bridge__$some_function(void);
460460
}
461461
}
462462

463+
/// Test code generation for Rust function that returns an Option<&OpaqueRustType>
464+
mod extern_rust_fn_return_option_ref_opaque_rust_type {
465+
use super::*;
466+
467+
fn bridge_module_tokens() -> TokenStream {
468+
quote! {
469+
mod ffi {
470+
extern "Rust" {
471+
type SomeType;
472+
fn some_function () -> Option<&SomeType>;
473+
}
474+
}
475+
}
476+
}
477+
478+
fn expected_rust_tokens() -> ExpectedRustTokens {
479+
ExpectedRustTokens::Contains(quote! {
480+
#[export_name = "__swift_bridge__$some_function"]
481+
pub extern "C" fn __swift_bridge__some_function() -> *const super::SomeType {
482+
if let Some(val) = super::some_function() {
483+
val as *const super::SomeType
484+
} else {
485+
std::ptr::null()
486+
}
487+
}
488+
})
489+
}
490+
491+
fn expected_swift_code() -> ExpectedSwiftCode {
492+
ExpectedSwiftCode::ContainsAfterTrim(
493+
r#"
494+
func some_function() -> Optional<SomeTypeRef> {
495+
{ let val = __swift_bridge__$some_function(); if val != nil { return SomeTypeRef(ptr: val!) } else { return nil } }()
496+
}
497+
"#,
498+
)
499+
}
500+
501+
fn expected_c_header() -> ExpectedCHeader {
502+
ExpectedCHeader::ContainsAfterTrim(
503+
r#"
504+
void* __swift_bridge__$some_function(void);
505+
"#,
506+
)
507+
}
508+
509+
#[test]
510+
fn extern_rust_fn_return_option_opaque_rust_type() {
511+
CodegenTest {
512+
bridge_module: bridge_module_tokens().into(),
513+
expected_rust_tokens: expected_rust_tokens(),
514+
expected_swift_code: expected_swift_code(),
515+
expected_c_header: expected_c_header(),
516+
}
517+
.test();
518+
}
519+
}
520+
521+
/// Test code generation for Rust function that returns an Option<&OpaqueRustType>
522+
mod extern_rust_fn_arg_option_ref_opaque_rust_type {
523+
use super::*;
524+
525+
fn bridge_module_tokens() -> TokenStream {
526+
quote! {
527+
mod ffi {
528+
extern "Rust" {
529+
type SomeType;
530+
fn some_function (arg: Option<&SomeType>);
531+
}
532+
}
533+
}
534+
}
535+
536+
fn expected_rust_tokens() -> ExpectedRustTokens {
537+
ExpectedRustTokens::Contains(quote! {
538+
#[export_name = "__swift_bridge__$some_function"]
539+
pub extern "C" fn __swift_bridge__some_function(arg: *const super::SomeType) {
540+
super::some_function(
541+
if arg.is_null() {
542+
None
543+
} else {
544+
Some( unsafe { & * arg })
545+
}
546+
)
547+
}
548+
})
549+
}
550+
551+
fn expected_swift_code() -> ExpectedSwiftCode {
552+
ExpectedSwiftCode::ContainsAfterTrim(
553+
r#"
554+
func some_function(_ arg: Optional<SomeTypeRef>) {
555+
__swift_bridge__$some_function({ if let val = arg { return val.ptr } else { return nil } }())
556+
}
557+
"#,
558+
)
559+
}
560+
561+
fn expected_c_header() -> ExpectedCHeader {
562+
ExpectedCHeader::ContainsAfterTrim(
563+
r#"
564+
void __swift_bridge__$some_function(void* arg);
565+
"#,
566+
)
567+
}
568+
569+
#[test]
570+
fn extern_rust_fn_return_option_opaque_rust_type() {
571+
CodegenTest {
572+
bridge_module: bridge_module_tokens().into(),
573+
expected_rust_tokens: expected_rust_tokens(),
574+
expected_swift_code: expected_swift_code(),
575+
expected_c_header: expected_c_header(),
576+
}
577+
.test();
578+
}
579+
}
580+
463581
/// Test code generation for Rust function that takes an Option<OpaqueRustType> argument.
464582
mod extern_rust_fn_with_option_opaque_rust_type_arg {
465583
use super::*;

crates/swift-integration-tests/src/option.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,15 @@ mod ffi {
3535

3636
extern "Rust" {
3737
type OptTestOpaqueRustType;
38+
type OptTestOpaqueRefRustType;
3839

3940
#[swift_bridge(init)]
4041
fn new(field: u8) -> OptTestOpaqueRustType;
41-
fn field(&self) -> u8;
42+
fn field(self: &OptTestOpaqueRustType) -> u8;
43+
44+
#[swift_bridge(associated_to = OptTestOpaqueRefRustType)]
45+
fn new(field: u8) -> OptTestOpaqueRefRustType;
46+
fn field_ref(self: &OptTestOpaqueRefRustType) -> Option<&OptTestOpaqueRustType>;
4247
}
4348

4449
extern "Rust" {
@@ -85,6 +90,10 @@ mod ffi {
8590
arg: Option<OptTestOpaqueRustType>,
8691
) -> Option<OptTestOpaqueRustType>;
8792

93+
fn rust_reflect_option_ref_opaque_rust_type(
94+
arg: Option<&OptTestOpaqueRustType>,
95+
) -> Option<&OptTestOpaqueRustType>;
96+
8897
fn rust_reflect_option_opaque_rust_copy_type(
8998
arg: Option<OptTestOpaqueRustCopyType>,
9099
) -> Option<OptTestOpaqueRustCopyType>;
@@ -178,6 +187,22 @@ impl OptTestOpaqueRustType {
178187
}
179188
}
180189

190+
pub struct OptTestOpaqueRefRustType {
191+
field: Option<OptTestOpaqueRustType>,
192+
}
193+
194+
impl OptTestOpaqueRefRustType {
195+
fn new(field: u8) -> Self {
196+
Self {
197+
field: Some(OptTestOpaqueRustType::new(field)),
198+
}
199+
}
200+
201+
fn field_ref(&self) -> Option<&OptTestOpaqueRustType> {
202+
self.field.as_ref()
203+
}
204+
}
205+
181206
#[derive(Copy, Clone)]
182207
pub struct OptTestOpaqueRustCopyType {
183208
#[allow(unused)]
@@ -243,6 +268,12 @@ fn rust_reflect_option_opaque_rust_type(
243268
arg
244269
}
245270

271+
fn rust_reflect_option_ref_opaque_rust_type(
272+
arg: Option<&OptTestOpaqueRustType>,
273+
) -> Option<&OptTestOpaqueRustType> {
274+
arg
275+
}
276+
246277
fn rust_reflect_option_opaque_rust_copy_type(
247278
arg: Option<OptTestOpaqueRustCopyType>,
248279
) -> Option<OptTestOpaqueRustCopyType> {

0 commit comments

Comments
 (0)