diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SharedStruct.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SharedStruct.swift index b723925d..36648391 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SharedStruct.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SharedStruct.swift @@ -11,7 +11,10 @@ func rust_calls_swift_struct_with_no_fields(arg: StructWithNoFields) -> StructWi arg } -func rust_calls_struct_repr_struct_one_primitive_field(arg: StructReprStructWithOnePrimitiveField) -> StructReprStructWithOnePrimitiveField { +func rust_calls_swift_struct_repr_struct_one_primitive_field(arg: StructReprStructWithOnePrimitiveField) -> StructReprStructWithOnePrimitiveField { arg } +func rust_calls_swift_struct_repr_struct_one_string_field(arg: StructReprStructWithOneStringField) -> StructReprStructWithOneStringField { + arg +} diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedStructTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedStructTests.swift index bbf8a1b4..c21f4ed7 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedStructTests.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedStructTests.swift @@ -34,6 +34,14 @@ class SharedStructTests: XCTestCase { XCTAssertEqual(val.named_field, 56) } + func testStructReprStructWithOneStringField() { + let val = rust_calls_swift_struct_repr_struct_one_string_field( + arg: StructReprStructWithOneStringField(field: "hello world".intoRustString()) + ); + XCTAssertEqual(val.field.toString(), "hello world") + } + + /// Verify that we can create a tuple struct. func testTupleStruct() { let val = StructReprStructTupleStruct(_0: 11, _1: 22) diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs index 8e710798..e6ed7866 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs @@ -259,11 +259,7 @@ impl ToTokens for SwiftBridgeModule { } let extern_swift_fn_tokens = if extern_swift_fn_tokens.len() > 0 { - quote! { - extern "C" { - #(#extern_swift_fn_tokens)* - } - } + generate_extern_c_block(extern_swift_fn_tokens) } else { quote! {} }; @@ -310,6 +306,81 @@ impl ToTokens for SwiftBridgeModule { } } +/// Generate an `extern "C"` block such as: +/// +/// ```no_run +/// extern "C" { +/// #[link_name = "some_swift_function_name"] +/// fn __swift_bridge__some_swift_function_name(); +/// } +/// ``` +/// +/// ## `improper_ctypes` lint suppression +/// +/// We suppress the `improper_ctypes` lint with `#[allow(improper_ctypes)]`. +/// +/// Given the following bridge module: +/// +/// ```ignore +/// #[swift_bridge::bridge] +/// mod ffi { +/// struct SomeStruct { +/// string: String +/// } +/// +/// extern "Swift" { +/// fn return_struct() -> SomeStruct; +/// } +/// } +/// ``` +/// +/// We would generate the following struct FFI representation and `extern "C"` block: +/// +/// ```no_run +/// struct __swift_bridge__SomeStruct { +/// string: *mut swift_bridge::string::RustString +/// } +/// +/// extern "C" { +/// #[link_name = "__swift_bridge__$rust_calls_swift_struct_repr_struct_one_string_field"] +/// fn __swift_bridge__return_struct() -> __swift_bridge__SomeStruct; +/// } +/// +/// # mod swift_bridge { pub mod string { pub struct RustString; }} +/// ``` +/// +/// The `__swift_bridge__SomeStruct` holds a pointer to a `RustString`. +/// +/// Since `RustString` is not FFI safe, and the Rust compiler cannot know that we only plan to use +/// the pointer as an opaque pointer, the Rust compiler emits an `improper_ctypes` lint. +/// +/// The risk is that if we ever do actually attempt to do something that is not FFI safe the +/// `#[allow(improper_ctypes)]` might prevent us from noticing. +/// +/// Given that our codegen is heavily tested we are not currently concerned about this. +/// +/// Should we become concerned about this in the future we could consider solutions such as: +/// +/// - Generate an `__swift_bridge__SomeStruct_INNER_OPAQUE` that only held opaque pointers. +/// We could then transmute the `__swift_bridge__SomeStruct` to/from this type when +/// passing/receiving it across the FFI boundary. +/// ``` +/// struct __swift_bridge__SomeStruct_INNER_OPAQUE { +/// string: *mut std::ffi::c_void +/// } +/// ``` +/// - This would involve generating an extra type, but given that they would have the same layout +/// and simply get transmuted into each other we could imagine that the optimizer would erase +/// all overhead. +fn generate_extern_c_block(extern_swift_fn_tokens: Vec) -> TokenStream { + quote! { + // #[allow(improper_ctypes)] + extern "C" { + #(#extern_swift_fn_tokens)* + } + } +} + #[cfg(test)] mod tests { //! More tests can be found in src/codegen/codegen_tests.rs and its submodules. diff --git a/crates/swift-integration-tests/src/expose_opaque_rust_type.rs b/crates/swift-integration-tests/src/expose_opaque_rust_type.rs index 0414e17d..7fdf20df 100644 --- a/crates/swift-integration-tests/src/expose_opaque_rust_type.rs +++ b/crates/swift-integration-tests/src/expose_opaque_rust_type.rs @@ -1,3 +1,15 @@ +#[swift_bridge::bridge] +mod foo { + #[swift_bridge(swift_repr = "struct")] + struct SharedStruct { + field: String, + } + + extern "Swift" { + fn swift_func() -> SharedStruct; + } +} + #[swift_bridge::bridge] mod ffi { extern "Rust" { diff --git a/crates/swift-integration-tests/src/shared_types/shared_struct.rs b/crates/swift-integration-tests/src/shared_types/shared_struct.rs index 20033321..6e484173 100644 --- a/crates/swift-integration-tests/src/shared_types/shared_struct.rs +++ b/crates/swift-integration-tests/src/shared_types/shared_struct.rs @@ -17,6 +17,11 @@ mod ffi { #[swift_bridge(swift_repr = "struct")] struct StructReprStructTupleStruct(u8, u32); + #[swift_bridge(swift_repr = "struct")] + struct StructReprStructWithOneStringField { + field: String, + } + extern "Rust" { fn test_rust_calls_swift(); @@ -34,15 +39,19 @@ mod ffi { extern "Swift" { fn rust_calls_swift_struct_with_no_fields(arg: StructWithNoFields) -> StructWithNoFields; - fn rust_calls_struct_repr_struct_one_primitive_field( + fn rust_calls_swift_struct_repr_struct_one_primitive_field( arg: StructReprStructWithOnePrimitiveField, ) -> StructReprStructWithOnePrimitiveField; + + fn rust_calls_swift_struct_repr_struct_one_string_field( + arg: StructReprStructWithOneStringField, + ) -> StructReprStructWithOneStringField; } } fn test_rust_calls_swift() { self::tests::test_rust_calls_swift_struct_with_no_fields(); - self::tests::test_rust_calls_struct_repr_struct_one_primitive_field(); + self::tests::test_rust_calls_swift_struct_repr_struct_one_primitive_field(); } fn swift_calls_rust_struct_with_no_fields(arg: ffi::StructWithNoFields) -> ffi::StructWithNoFields { @@ -70,10 +79,10 @@ mod tests { ffi::rust_calls_swift_struct_with_no_fields(ffi::StructWithNoFields); } - pub(super) fn test_rust_calls_struct_repr_struct_one_primitive_field() { + pub(super) fn test_rust_calls_swift_struct_repr_struct_one_primitive_field() { let arg = ffi::StructReprStructWithOnePrimitiveField { named_field: 10 }; - let val = ffi::rust_calls_struct_repr_struct_one_primitive_field(arg); + let val = ffi::rust_calls_swift_struct_repr_struct_one_primitive_field(arg); assert_eq!(val.named_field, 10); }