From 5cc975e9a3886e656abac421316112fe6c06ea89 Mon Sep 17 00:00:00 2001 From: Chinedu Francis Nwafili Date: Tue, 4 Feb 2025 11:52:38 -0500 Subject: [PATCH] Add `#[swift_bridge(Sendable)]` attribute This commit introduces a new `#[swift_bridge(Sendable)]` attribute that emits Swift `Sendable` implementations for opaque Rust types, and Rust `Send + Sync` implementations for opaque Swift types. These implementations are checked at compile time. Example usage: ```rust mod ffi { extern "Rust" { #[swift_bridge(Sendable)] type MyRustType; } extern "Swift" { #[swift_bridge(Sendable)] type MySwiftType; } } ``` --- .../project.pbxproj | 30 +++-- .../SendableAttribute.swift | 12 ++ ...ft => AlreadyDeclaredAttributeTests.swift} | 2 +- .../SendableAttributeTests.swift | 30 +++++ book/src/bridge-module/opaque-types/README.md | 25 ++++ .../src/codegen/codegen_tests.rs | 5 +- ...ruct_attribute.rs => derive_copy_clone.rs} | 0 .../{derive_attribute.rs => derive_debug.rs} | 0 .../codegen_tests/sendable_attribute.rs | 109 ++++++++++++++++++ .../src/codegen/generate_c_header.rs | 6 + .../src/codegen/generate_rust_tokens.rs | 55 ++++++++- .../src/codegen/generate_swift.rs | 108 +++++++++++------ .../opaque_type_attributes.rs | 36 ++++++ .../src/parse/type_declarations.rs | 4 + crates/swift-integration-tests/src/lib.rs | 1 + .../src/sendable_attribute.rs | 47 ++++++++ 16 files changed, 420 insertions(+), 50 deletions(-) create mode 100644 SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SendableAttribute.swift rename SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/{OpaqueTypeAttributeTests.swift => AlreadyDeclaredAttributeTests.swift} (97%) create mode 100644 SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SendableAttributeTests.swift rename crates/swift-bridge-ir/src/codegen/codegen_tests/{derive_struct_attribute.rs => derive_copy_clone.rs} (100%) rename crates/swift-bridge-ir/src/codegen/codegen_tests/{derive_attribute.rs => derive_debug.rs} (100%) create mode 100644 crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs create mode 100644 crates/swift-integration-tests/src/sendable_attribute.rs diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj index 07afdcc4..6653dd67 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj @@ -21,7 +21,7 @@ 220432EC27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */; }; 22046383282B4E3F00A09119 /* FunctionAttributeGetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22046382282B4E3F00A09119 /* FunctionAttributeGetTests.swift */; }; 221E16B42786233600F94AC0 /* ConditionalCompilationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */; }; - 221E16B62786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */; }; + 221E16B62786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B52786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift */; }; 222A81E928EB5BB100D4A412 /* Primitive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222A81E828EB5BB100D4A412 /* Primitive.swift */; }; 222A81EB28EB5DF800D4A412 /* PrimitiveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222A81EA28EB5DF800D4A412 /* PrimitiveTests.swift */; }; 22553324281DB5FC008A3121 /* GenericTests.rs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22553323281DB5FC008A3121 /* GenericTests.rs.swift */; }; @@ -57,6 +57,8 @@ 22FD1C562753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */; }; C926E4DE294F07AA0027E7E2 /* FunctionAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C926E4DD294F07AA0027E7E2 /* FunctionAttributes.swift */; }; C926E4E0294F18C50027E7E2 /* FunctionAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */; }; + DE9444DC2D5241AD007A83A4 /* SendableAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9444DB2D5241A8007A83A4 /* SendableAttributeTests.swift */; }; + DE9444DE2D527C3B007A83A4 /* SendableAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9444DD2D527C31007A83A4 /* SendableAttribute.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -84,7 +86,7 @@ 220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustFnUsesOpaqueSwiftTypeTests.swift; sourceTree = ""; }; 22046382282B4E3F00A09119 /* FunctionAttributeGetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionAttributeGetTests.swift; sourceTree = ""; }; 221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalCompilationTests.swift; sourceTree = ""; }; - 221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpaqueTypeAttributeTests.swift; sourceTree = ""; }; + 221E16B52786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlreadyDeclaredAttributeTests.swift; sourceTree = ""; }; 222A81E828EB5BB100D4A412 /* Primitive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Primitive.swift; sourceTree = ""; }; 222A81EA28EB5DF800D4A412 /* PrimitiveTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimitiveTests.swift; sourceTree = ""; }; 22553323281DB5FC008A3121 /* GenericTests.rs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericTests.rs.swift; sourceTree = ""; }; @@ -126,6 +128,8 @@ 22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftFnUsesOpaqueRustTypeTests.swift; sourceTree = ""; }; C926E4DD294F07AA0027E7E2 /* FunctionAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionAttributes.swift; sourceTree = ""; }; C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionAttributeTests.swift; sourceTree = ""; }; + DE9444DB2D5241A8007A83A4 /* SendableAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableAttributeTests.swift; sourceTree = ""; }; + DE9444DD2D527C31007A83A4 /* SendableAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableAttribute.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -189,6 +193,7 @@ 22EE4E0828B5388000FEC83C /* SwiftFnUsesOpaqueSwiftType.swift */, 22C0625228CE699D007A6F67 /* Callbacks.swift */, 225908FD28DA0F9F0080C737 /* Result.swift */, + DE9444DD2D527C31007A83A4 /* SendableAttribute.swift */, 22BC4BBB294BA0EC0032B8A8 /* SharedEnumAttributes.swift */, C926E4DD294F07AA0027E7E2 /* FunctionAttributes.swift */, 1784BE2729CE86D600AE5A4A /* Tuple.swift */, @@ -208,32 +213,33 @@ 228FE5E52740DB6D00805D9E /* SwiftRustIntegrationTestRunnerTests */ = { isa = PBXGroup; children = ( + 221E16B52786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift */, + 178F1CD2298E97FB00335AA0 /* ArgumentAttributesTest.swift */, 22D092A227B7E865009A4C2B /* AsyncTests.swift */, + 22C0625428CE6C9A007A6F67 /* CallbackTests.swift */, 221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */, + C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */, 22046382282B4E3F00A09119 /* FunctionAttributeGetTests.swift */, 22BCAAB827A2607700686A21 /* FunctionAttributeIdentifiableTests.swift */, + 22553323281DB5FC008A3121 /* GenericTests.rs.swift */, 228FE60F27416C0300805D9E /* OpaqueRustStructTests.swift */, 228FE61127428A8D00805D9E /* OpaqueSwiftStructTests.swift */, - 221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */, + 220432A6274C953E00BAE645 /* PointerTests.swift */, 22043294274ADA7A00BAE645 /* OptionTests.swift */, 225908FB28DA0E320080C737 /* ResultTests.swift */, - 220432A6274C953E00BAE645 /* PointerTests.swift */, 222A81EA28EB5DF800D4A412 /* PrimitiveTests.swift */, 220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */, + DE9444DB2D5241A8007A83A4 /* SendableAttributeTests.swift */, 2202BC0727B2DD1700D43CC4 /* SharedEnumTests.swift */, 22BC4BB9294B8CCD0032B8A8 /* SharedEnumAttributeTests.swift */, 22C0AD50278ECA9E00A96469 /* SharedStructAttributeTests.swift */, 220432AE274E7BF800BAE645 /* SharedStructTests.swift */, + 2289E82B29A879A7009D89D7 /* SingleRepresentationTypeElisionTests.swift */, 228FE5E62740DB6D00805D9E /* StringTests.swift */, 22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */, - 22043292274A8FDF00BAE645 /* VecTests.swift */, - 22553323281DB5FC008A3121 /* GenericTests.rs.swift */, - 2289E82B29A879A7009D89D7 /* SingleRepresentationTypeElisionTests.swift */, 22EE4E0A28B538A700FEC83C /* SwiftFnUsesOpaqueSwiftTypeTests.swift */, - 22C0625428CE6C9A007A6F67 /* CallbackTests.swift */, - C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */, - 178F1CD2298E97FB00335AA0 /* ArgumentAttributesTest.swift */, 1745111429BE189B00B96A1A /* TupleTests.swift */, + 22043292274A8FDF00BAE645 /* VecTests.swift */, ); path = SwiftRustIntegrationTestRunnerTests; sourceTree = ""; @@ -407,6 +413,7 @@ C926E4DE294F07AA0027E7E2 /* FunctionAttributes.swift in Sources */, 228FE64A274919C600805D9E /* swift-integration-tests.swift in Sources */, 22C0625328CE699D007A6F67 /* Callbacks.swift in Sources */, + DE9444DE2D527C3B007A83A4 /* SendableAttribute.swift in Sources */, 22BC10F82799A3A000A0D046 /* SharedStructAttributes.swift in Sources */, 22EE4E0928B5388000FEC83C /* SwiftFnUsesOpaqueSwiftType.swift in Sources */, 228FE60C2740F42000805D9E /* ASwiftStack.swift in Sources */, @@ -418,12 +425,13 @@ buildActionMask = 2147483647; files = ( 22043293274A8FDF00BAE645 /* VecTests.swift in Sources */, - 221E16B62786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift in Sources */, + 221E16B62786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift in Sources */, 220432A7274C953E00BAE645 /* PointerTests.swift in Sources */, C926E4E0294F18C50027E7E2 /* FunctionAttributeTests.swift in Sources */, 220432AF274E7BF800BAE645 /* SharedStructTests.swift in Sources */, 178F1CD3298E97FB00335AA0 /* ArgumentAttributesTest.swift in Sources */, 2289E82C29A879A7009D89D7 /* SingleRepresentationTypeElisionTests.swift in Sources */, + DE9444DC2D5241AD007A83A4 /* SendableAttributeTests.swift in Sources */, 220432EC27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift in Sources */, 222A81EB28EB5DF800D4A412 /* PrimitiveTests.swift in Sources */, 22553324281DB5FC008A3121 /* GenericTests.rs.swift in Sources */, diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SendableAttribute.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SendableAttribute.swift new file mode 100644 index 00000000..99eae4a8 --- /dev/null +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SendableAttribute.swift @@ -0,0 +1,12 @@ +// +// SendableAttribute.swift +// SwiftRustIntegrationTestRunner +// +// Created by Frankie Nwafili on 2/4/25. +// + +final class SendableSwiftType {} +extension SendableSwiftType: Sendable {} + +final class AnotherSendableSwiftType {} +extension AnotherSendableSwiftType: Sendable {} diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OpaqueTypeAttributeTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/AlreadyDeclaredAttributeTests.swift similarity index 97% rename from SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OpaqueTypeAttributeTests.swift rename to SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/AlreadyDeclaredAttributeTests.swift index 16ce2d4d..4b599d27 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OpaqueTypeAttributeTests.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/AlreadyDeclaredAttributeTests.swift @@ -1,5 +1,5 @@ // -// OpaqueTypeAttributeTests.swift +// AlreadyDeclaredAttributeTests.swift // SwiftRustIntegrationTestRunnerTests // // Created by Frankie Nwafili on 1/6/22. diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SendableAttributeTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SendableAttributeTests.swift new file mode 100644 index 00000000..a5417ef6 --- /dev/null +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SendableAttributeTests.swift @@ -0,0 +1,30 @@ +// +// SendableAttributeTests.swift +// SwiftRustIntegrationTestRunner +// +// Created by Frankie Nwafili on 2/4/25. +// + +import XCTest +@testable import SwiftRustIntegrationTestRunner + +/// Tests for the `#[swift_bridge(Sendable)]` attribute. +/// +/// For the corresponding Rust code, see `crates/swift-integration-tests/src/sendable_attribute.rs`. +/// For the corresponding codegen tests, see `crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs`. +class SendableAttributeTests: XCTestCase { + /// Verify that we can a Rust type that has the `#[swift_bridge(Sendable)]` attribute gets a `Sendable` protocol implementation. + func testSendableExternRustType() throws { + let sendableRustType = SendableRustType() + + // Move the type to another thread. + Thread.detachNewThread { + let _ = sendableRustType; + } + } +} + + +protocol AssertIsSendable: Sendable {} +extension SendableRustType: AssertIsSendable{} + diff --git a/book/src/bridge-module/opaque-types/README.md b/book/src/bridge-module/opaque-types/README.md index 05b839af..36f2a37e 100644 --- a/book/src/bridge-module/opaque-types/README.md +++ b/book/src/bridge-module/opaque-types/README.md @@ -266,6 +266,31 @@ table[val] = "world" print(table[val]) ``` +#### #[swift_bridge(Sendable)] + +The `Sendable` attribute can be added to both opaque Rust and opaque Swift types. + +When applied to an opaque Rust type, the generated Swift type will implement Swift's `Sendable` protocol. +`swift-bridge` will emit code that, at compile time, confirms that the Rust type implements `Send + Sync`. + +When applied to an opaque Swift type, the generated Rust type will implement Rust's `Send + Sync` traits. +`swift-bridge` will emit code that, at compile time, confirms that the Swift type implements `Sendable`. + +```rust +#[swift_bridge::bridge] +mod ffi { + extern "Rust" { + #[swift_bridge(Sendable)] + type MyRustType; + } + + extern "Swift" { + #[swift_bridge(Sendable)] + type MySwiftType; + } +} +``` + #### #[swift_bridge(__experimental_ownership)] The `__experimental_ownership` attribute instructs `swift-bridge` to emit code that takes advantage of Swift 6's diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests.rs index 27e1048f..8b0c440a 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests.rs @@ -34,8 +34,8 @@ mod boxed_fnonce; mod built_in_tuple; mod c_header_declaration_order; mod conditional_compilation; -mod derive_attribute; -mod derive_struct_attribute; +mod derive_copy_clone; +mod derive_debug; mod extern_rust_function_opaque_rust_type_argument; mod extern_rust_function_opaque_rust_type_return; mod extern_rust_method_swift_class_placement; @@ -46,6 +46,7 @@ mod opaque_swift_type; mod option; mod result; mod return_into_attribute; +mod sendable_attribute; mod single_representation_type_elision; mod string; mod transparent_enum; diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_copy_clone.rs similarity index 100% rename from crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute.rs rename to crates/swift-bridge-ir/src/codegen/codegen_tests/derive_copy_clone.rs diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_attribute.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_debug.rs similarity index 100% rename from crates/swift-bridge-ir/src/codegen/codegen_tests/derive_attribute.rs rename to crates/swift-bridge-ir/src/codegen/codegen_tests/derive_debug.rs diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs new file mode 100644 index 00000000..dcc029c1 --- /dev/null +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs @@ -0,0 +1,109 @@ +use super::{ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode}; +use proc_macro2::TokenStream; +use quote::quote; + +/// Verify that we can add `#[swift_bridge(Sendable)]` to an extern Rust type. +mod extern_rust_sendable_attribute { + use super::*; + use crate::codegen::codegen_tests::CodegenTest; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + extern "Rust" { + #[swift_bridge(Sendable)] + type SomeType; + } + } + } + } + + /// Verify that we generate a function that frees the memory behind an opaque pointer to a Rust + /// type. + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + const fn __swift_bridge__assert_send_sync() {} + const _: () = { __swift_bridge__assert_send_sync::() }; + }) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsAfterTrim( + r#" +extension SomeType: @unchecked Sendable {} +"#, + ) + } + + const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim( + r#" +typedef struct SomeType SomeType; +void __swift_bridge__$SomeType$_free(void* self); + "#, + ); + + #[test] + fn extern_rust_type() { + CodegenTest { + bridge_module: bridge_module_tokens().into(), + expected_rust_tokens: expected_rust_tokens(), + expected_swift_code: expected_swift_code(), + expected_c_header: EXPECTED_C_HEADER, + } + .test(); + } +} + +/// Verify that we can add `#[swift_bridge(Sendable)]` to an extern Swift type. +mod extern_swift_sendable_attribute { + use super::*; + use crate::codegen::codegen_tests::CodegenTest; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod foo { + extern "Swift" { + #[swift_bridge(Sendable)] + type SomeSwiftType; + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + #[repr(C)] + pub struct SomeSwiftType(*mut std::ffi::c_void); + + impl Drop for SomeSwiftType { + fn drop (&mut self) { + unsafe { __swift_bridge__SomeSwiftType__free(self.0) } + } + } + + unsafe impl Send for SomeSwiftType {} + unsafe impl Sync for SomeSwiftType {} + + }) + } + + const EXPECTED_SWIFT_CODE: ExpectedSwiftCode = ExpectedSwiftCode::ContainsAfterTrim( + r#" +protocol __swift_bridge__IsSendable: Sendable {} +extension SomeSwiftType: __swift_bridge__IsSendable {} +"#, + ); + + const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim(r#""#); + + #[test] + fn extern_swift_type_derive_sendable() { + CodegenTest { + bridge_module: bridge_module_tokens().into(), + expected_rust_tokens: expected_rust_tokens(), + expected_swift_code: EXPECTED_SWIFT_CODE, + expected_c_header: EXPECTED_C_HEADER, + } + .test(); + } +} diff --git a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs index aba38a7a..543db318 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs @@ -523,6 +523,12 @@ fn declare_func( #[cfg(test)] mod tests { //! More tests can be found in src/codegen/codegen_tests.rs and its submodules. + //! + //! TODO: Gradually delete these tests and replace them with tests in the existing + //! `mod codegen_tests`. + //! This way we have one place to analyze the related Rust+Swift+C generated code + //! vs. currently needing to look at `generate_swift.rs` `generate_c.rs` and `generate_rust.rs` + //! to get a full picture of the codegen. use proc_macro2::TokenStream; use quote::quote; 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 ce6a38c7..e7d43796 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs @@ -8,7 +8,9 @@ use quote::{quote, quote_spanned}; use self::vec::vec_of_opaque_rust_type::generate_vec_of_opaque_rust_type_functions; use crate::bridge_module_attributes::CfgAttr; -use crate::parse::{HostLang, SharedTypeDeclaration, TypeDeclaration}; +use crate::parse::{ + HostLang, OpaqueForeignTypeDeclaration, SharedTypeDeclaration, TypeDeclaration, +}; use crate::SwiftBridgeModule; mod shared_enum; @@ -33,6 +35,8 @@ impl ToTokens for SwiftBridgeModule { let mut freestanding_rust_call_swift_fn_tokens = vec![]; let mut extern_swift_fn_tokens = vec![]; + let mut has_encountered_at_least_one_rust_sendable_type = false; + for func in &self.functions { match func.host_lang { HostLang::Rust => { @@ -215,6 +219,19 @@ impl ToTokens for SwiftBridgeModule { generate_vec_of_opaque_rust_type_functions(ty_name); extern_rust_fn_tokens.push(vec_functions); } + + if ty.attributes.sendable { + if !has_encountered_at_least_one_rust_sendable_type { + extern_rust_fn_tokens.push( + generate_extern_rust_type_send_sync_checker() + ); + + has_encountered_at_least_one_rust_sendable_type = true; + } + + extern_rust_fn_tokens + .push(generate_extern_rust_type_send_sync_check(ty)); + } } } } @@ -234,6 +251,15 @@ impl ToTokens for SwiftBridgeModule { } }; + let maybe_impl_send_sync = if ty.attributes.sendable { + quote! { + unsafe impl Send for #ty_name {} + unsafe impl Sync for #ty_name {} + } + } else { + quote! {} + }; + let struct_tokens = quote! { #[repr(C)] pub struct #ty_name(*mut std::ffi::c_void); @@ -245,6 +271,8 @@ impl ToTokens for SwiftBridgeModule { unsafe { #free_mem_func_name(self.0) } } } + + #maybe_impl_send_sync }; structs_for_swift_classes.push(struct_tokens); @@ -384,9 +412,34 @@ fn generate_extern_c_block(extern_swift_fn_tokens: Vec) -> TokenStr } } +/// Generate a function that can be used to check at compile time that a type implements +/// `Send + Sync`. +fn generate_extern_rust_type_send_sync_checker() -> TokenStream { + quote! { + const fn __swift_bridge__assert_send_sync() {} + } +} + +/// Generate code that checks at compile time that a particular type implements `Send + Sync`. +fn generate_extern_rust_type_send_sync_check(ty: &OpaqueForeignTypeDeclaration) -> TokenStream { + let ty_name = ty.ty_name_ident(); + + quote! { + const _: () = { + __swift_bridge__assert_send_sync::() + }; + } +} + #[cfg(test)] mod tests { //! More tests can be found in src/codegen/codegen_tests.rs and its submodules. + //! + //! TODO: Gradually delete these tests and replace them with tests in the existing + //! `mod codegen_tests`. + //! This way we have one place to analyze the related Rust+Swift+C generated code + //! vs. currently needing to look at `generate_swift.rs` `generate_c.rs` and `generate_rust.rs` + //! to get a full picture of the codegen. use quote::quote; diff --git a/crates/swift-bridge-ir/src/codegen/generate_swift.rs b/crates/swift-bridge-ir/src/codegen/generate_swift.rs index 491cd818..48e67988 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_swift.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_swift.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; - use syn::Path; use crate::bridged_type::{BridgeableType, BridgedType, TypePosition}; @@ -36,6 +35,8 @@ impl SwiftBridgeModule { HashMap::new(); let mut class_protocols: HashMap = HashMap::new(); + let mut has_encountered_at_least_one_sendable_swift_type = false; + for function in &self.functions { if function.host_lang.is_rust() { if let Some(ty) = function.associated_type.as_ref() { @@ -102,46 +103,65 @@ impl SwiftBridgeModule { swift += "\n"; } } - TypeDeclaration::Opaque(ty) => match ty.host_lang { - HostLang::Rust => { - if let Some(_copy) = ty.attributes.copy { - swift += &generate_opaque_copy_struct( - ty, - &associated_funcs_and_methods, - &self.types, - &self.swift_bridge_path, - ); - } else { - let class_protocols = class_protocols.get(&ty.ty.to_string()); - let default_cp = ClassProtocols::default(); - let class_protocols = class_protocols.unwrap_or(&default_cp); - - swift += &generate_swift_class( - ty, - &associated_funcs_and_methods, - class_protocols, - &self.types, - &self.swift_bridge_path, - ); + TypeDeclaration::Opaque(ty) => { + match ty.host_lang { + HostLang::Rust => { + if let Some(_copy) = ty.attributes.copy { + swift += &generate_opaque_copy_struct( + ty, + &associated_funcs_and_methods, + &self.types, + &self.swift_bridge_path, + ); + } else { + let class_protocols = class_protocols.get(&ty.ty.to_string()); + let default_cp = ClassProtocols::default(); + let class_protocols = class_protocols.unwrap_or(&default_cp); + + swift += &generate_swift_class( + ty, + &associated_funcs_and_methods, + class_protocols, + &self.types, + &self.swift_bridge_path, + ); + } + + swift += "\n"; + + if !ty.attributes.already_declared { + // TODO: Support Vec. Add codegen tests and then + // make them pass. + // TODO: Support Vec { + swift += &generate_drop_swift_instance_reference_count(ty); - swift += "\n"; + if ty.attributes.sendable { + if !has_encountered_at_least_one_sendable_swift_type { + swift += create_swift_sendable_protocol_check(); + swift += "\n"; - if !ty.attributes.already_declared { - // TODO: Support Vec. Add codegen tests and then - // make them pass. - // TODO: Support Vec { - swift += &generate_drop_swift_instance_reference_count(ty); - swift += "\n"; - } - }, + } }; } @@ -385,9 +405,27 @@ fn generate_swift_class_methods( } } +/// A Swift protocol that inherits from Swift's `Sendable` protocol. +/// We use this to validate at compile time that a Swift type is `Sendable`. +fn create_swift_sendable_protocol_check() -> &'static str { + "protocol __swift_bridge__IsSendable: Sendable {}" +} + +/// A Swift protocol that inherits from Swift's `Sendable` protocol. +/// We use this to validate at compile time that a Swift type is `Sendable`. +fn implement_swift_sendable_protocol(ty: &OpaqueForeignTypeDeclaration) -> String { + let ty_name = ty.ty_name_string(); + format!("extension {ty_name}: __swift_bridge__IsSendable {{}}") +} + #[cfg(test)] mod tests { //! More tests can be found in src/codegen/codegen_tests.rs and its submodules. + //! TODO: Gradually delete these tests and replace them with tests in the existing + //! `mod codegen_tests`. + //! This way we have one place to analyze the related Rust+Swift+C generated code + //! vs. currently needing to look at `generate_swift.rs` `generate_c.rs` and `generate_rust.rs` + //! to get a full picture of the codegen. use crate::codegen::generate_swift::CodegenConfig; use quote::quote; diff --git a/crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs b/crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs index 870a63c3..30868bfb 100644 --- a/crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs +++ b/crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs @@ -32,6 +32,16 @@ pub(crate) struct OpaqueTypeSwiftBridgeAttributes { /// `#[swift_bridge(Hashable)]` /// Used to determine if Hashable need to be implemented. pub hashable: bool, + /// `#[swift_bridge(Sendable)]` + /// Used to determine if the generated type should implement the Swift's `Sendable` protocol or + /// Rust's `Send+Sync` trait. + /// + /// When applied to a Rust type the generated Swift code will implement `Sendable`. + /// Compile time checks get emitted to ensure that the Rust type is `Send + Sync`. + /// + /// When applied to a Swift type the generated Swift code will implement `Send + Sync`. + /// Compile time checks get emitted to ensure that the Swift type is `Sendable`. + pub sendable: bool, /// `#[swift_bridge(__experimental_swift_ownership)]` /// Enables experimental support for Swift ownership. /// This attribute will eventually be removed once we've stabilized our support for Swift @@ -83,6 +93,7 @@ impl OpaqueTypeSwiftBridgeAttributes { OpaqueTypeAttr::DeclareGeneric => self.declare_generic = true, OpaqueTypeAttr::Equatable => self.equatable = true, OpaqueTypeAttr::Hashable => self.hashable = true, + OpaqueTypeAttr::Sendable => self.sendable = true, OpaqueTypeAttr::ExperimentalSwiftOwnership => self.experimental_swift_ownership = true, } } @@ -94,6 +105,7 @@ pub(crate) enum OpaqueTypeAttr { DeclareGeneric, Equatable, Hashable, + Sendable, ExperimentalSwiftOwnership, } @@ -132,6 +144,7 @@ impl Parse for OpaqueTypeAttr { "declare_generic" => OpaqueTypeAttr::DeclareGeneric, "Equatable" => OpaqueTypeAttr::Equatable, "Hashable" => OpaqueTypeAttr::Hashable, + "Sendable" => OpaqueTypeAttr::Sendable, "__experimental_swift_ownership" => OpaqueTypeAttr::ExperimentalSwiftOwnership, _ => { let attrib = key.to_string(); @@ -275,6 +288,29 @@ mod tests { assert_eq!(attribs.experimental_swift_ownership, true); } + /// Verify that we parse a Rust or Swift opaque type's `Sendable` attribute. + #[test] + fn parse_sendable_attribute() { + let tokens = quote! { + mod foo { + extern "Rust" { + #[swift_bridge(Sendable)] + type SomeRustType; + } + + extern "Swift" { + #[swift_bridge(Sendable)] + type SomeSwiftType; + } + } + }; + + for ty_name in ["SomeRustType", "SomeSwiftType"] { + let attribs = unwrap_opaque_type_attributes(tokens.clone(), ty_name); + assert_eq!(attribs.sendable, true); + } + } + fn unwrap_opaque_type_attributes( tokens: TokenStream, type_name: &'static str, diff --git a/crates/swift-bridge-ir/src/parse/type_declarations.rs b/crates/swift-bridge-ir/src/parse/type_declarations.rs index a6853692..053452fa 100644 --- a/crates/swift-bridge-ir/src/parse/type_declarations.rs +++ b/crates/swift-bridge-ir/src/parse/type_declarations.rs @@ -205,6 +205,10 @@ impl OpaqueForeignTypeDeclaration { pub fn ty_name_ident(&self) -> &Ident { &self.ty } + + pub fn ty_name_string(&self) -> String { + self.ty.to_string() + } } impl TypeDeclarations { diff --git a/crates/swift-integration-tests/src/lib.rs b/crates/swift-integration-tests/src/lib.rs index 3b14d067..65683aaf 100644 --- a/crates/swift-integration-tests/src/lib.rs +++ b/crates/swift-integration-tests/src/lib.rs @@ -22,6 +22,7 @@ mod vec; mod enum_attributes; mod function_attributes; mod opaque_type_attributes; +mod sendable_attribute; mod struct_attributes; mod futures_experiment; diff --git a/crates/swift-integration-tests/src/sendable_attribute.rs b/crates/swift-integration-tests/src/sendable_attribute.rs new file mode 100644 index 00000000..75a77797 --- /dev/null +++ b/crates/swift-integration-tests/src/sendable_attribute.rs @@ -0,0 +1,47 @@ +//! See `crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs` for codegen tests. + +#[swift_bridge::bridge] +mod ffi { + extern "Rust" { + #[swift_bridge(Sendable)] + type SendableRustType; + #[swift_bridge(init)] + fn new() -> SendableRustType; + + // Verify that our generated code does not emit any duplicate symbols. + // + // We accomplish this by defining this second `Sendable` type. + // If our code compiles then we know there weren't any duplicate codegen. + #[swift_bridge(Sendable)] + type AnotherSendableRustType; + } + + extern "Swift" { + #[swift_bridge(Sendable)] + type SendableSwiftType; + + // Verify that our generated code does not emit any duplicate symbols. + // + // We accomplish this by defining this second `Sendable` type. + // If our code compiles then we know there weren't any duplicate codegen. + #[swift_bridge(Sendable)] + type AnotherSendableSwiftType; + } +} + +struct SendableRustType; +impl SendableRustType { + fn new() -> Self { + Self + } +} + +struct AnotherSendableRustType; + +const fn assert_send_sync() {} + +// Verify that the `SendableSwiftType` implements `Send + Sync`. +#[allow(unused)] +const TEST_SENDABLE_SWIFT_TYPE_SEND_SYNC: () = { + assert_send_sync::(); +};