Skip to content

Commit 4827500

Browse files
authoredFeb 4, 2025··
Add #[swift_bridge(Sendable)] attribute (#317)
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; } } ```
1 parent c15279c commit 4827500

File tree

16 files changed

+420
-50
lines changed

16 files changed

+420
-50
lines changed
 

‎SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj

+19-11
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
220432EC27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */; };
2222
22046383282B4E3F00A09119 /* FunctionAttributeGetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22046382282B4E3F00A09119 /* FunctionAttributeGetTests.swift */; };
2323
221E16B42786233600F94AC0 /* ConditionalCompilationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */; };
24-
221E16B62786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */; };
24+
221E16B62786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B52786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift */; };
2525
222A81E928EB5BB100D4A412 /* Primitive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222A81E828EB5BB100D4A412 /* Primitive.swift */; };
2626
222A81EB28EB5DF800D4A412 /* PrimitiveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222A81EA28EB5DF800D4A412 /* PrimitiveTests.swift */; };
2727
22553324281DB5FC008A3121 /* GenericTests.rs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22553323281DB5FC008A3121 /* GenericTests.rs.swift */; };
@@ -57,6 +57,8 @@
5757
22FD1C562753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */; };
5858
C926E4DE294F07AA0027E7E2 /* FunctionAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C926E4DD294F07AA0027E7E2 /* FunctionAttributes.swift */; };
5959
C926E4E0294F18C50027E7E2 /* FunctionAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */; };
60+
DE9444DC2D5241AD007A83A4 /* SendableAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9444DB2D5241A8007A83A4 /* SendableAttributeTests.swift */; };
61+
DE9444DE2D527C3B007A83A4 /* SendableAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9444DD2D527C31007A83A4 /* SendableAttribute.swift */; };
6062
/* End PBXBuildFile section */
6163

6264
/* Begin PBXContainerItemProxy section */
@@ -84,7 +86,7 @@
8486
220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustFnUsesOpaqueSwiftTypeTests.swift; sourceTree = "<group>"; };
8587
22046382282B4E3F00A09119 /* FunctionAttributeGetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionAttributeGetTests.swift; sourceTree = "<group>"; };
8688
221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalCompilationTests.swift; sourceTree = "<group>"; };
87-
221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpaqueTypeAttributeTests.swift; sourceTree = "<group>"; };
89+
221E16B52786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlreadyDeclaredAttributeTests.swift; sourceTree = "<group>"; };
8890
222A81E828EB5BB100D4A412 /* Primitive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Primitive.swift; sourceTree = "<group>"; };
8991
222A81EA28EB5DF800D4A412 /* PrimitiveTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimitiveTests.swift; sourceTree = "<group>"; };
9092
22553323281DB5FC008A3121 /* GenericTests.rs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericTests.rs.swift; sourceTree = "<group>"; };
@@ -126,6 +128,8 @@
126128
22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftFnUsesOpaqueRustTypeTests.swift; sourceTree = "<group>"; };
127129
C926E4DD294F07AA0027E7E2 /* FunctionAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionAttributes.swift; sourceTree = "<group>"; };
128130
C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionAttributeTests.swift; sourceTree = "<group>"; };
131+
DE9444DB2D5241A8007A83A4 /* SendableAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableAttributeTests.swift; sourceTree = "<group>"; };
132+
DE9444DD2D527C31007A83A4 /* SendableAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableAttribute.swift; sourceTree = "<group>"; };
129133
/* End PBXFileReference section */
130134

131135
/* Begin PBXFrameworksBuildPhase section */
@@ -189,6 +193,7 @@
189193
22EE4E0828B5388000FEC83C /* SwiftFnUsesOpaqueSwiftType.swift */,
190194
22C0625228CE699D007A6F67 /* Callbacks.swift */,
191195
225908FD28DA0F9F0080C737 /* Result.swift */,
196+
DE9444DD2D527C31007A83A4 /* SendableAttribute.swift */,
192197
22BC4BBB294BA0EC0032B8A8 /* SharedEnumAttributes.swift */,
193198
C926E4DD294F07AA0027E7E2 /* FunctionAttributes.swift */,
194199
1784BE2729CE86D600AE5A4A /* Tuple.swift */,
@@ -208,32 +213,33 @@
208213
228FE5E52740DB6D00805D9E /* SwiftRustIntegrationTestRunnerTests */ = {
209214
isa = PBXGroup;
210215
children = (
216+
221E16B52786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift */,
217+
178F1CD2298E97FB00335AA0 /* ArgumentAttributesTest.swift */,
211218
22D092A227B7E865009A4C2B /* AsyncTests.swift */,
219+
22C0625428CE6C9A007A6F67 /* CallbackTests.swift */,
212220
221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */,
221+
C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */,
213222
22046382282B4E3F00A09119 /* FunctionAttributeGetTests.swift */,
214223
22BCAAB827A2607700686A21 /* FunctionAttributeIdentifiableTests.swift */,
224+
22553323281DB5FC008A3121 /* GenericTests.rs.swift */,
215225
228FE60F27416C0300805D9E /* OpaqueRustStructTests.swift */,
216226
228FE61127428A8D00805D9E /* OpaqueSwiftStructTests.swift */,
217-
221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */,
227+
220432A6274C953E00BAE645 /* PointerTests.swift */,
218228
22043294274ADA7A00BAE645 /* OptionTests.swift */,
219229
225908FB28DA0E320080C737 /* ResultTests.swift */,
220-
220432A6274C953E00BAE645 /* PointerTests.swift */,
221230
222A81EA28EB5DF800D4A412 /* PrimitiveTests.swift */,
222231
220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */,
232+
DE9444DB2D5241A8007A83A4 /* SendableAttributeTests.swift */,
223233
2202BC0727B2DD1700D43CC4 /* SharedEnumTests.swift */,
224234
22BC4BB9294B8CCD0032B8A8 /* SharedEnumAttributeTests.swift */,
225235
22C0AD50278ECA9E00A96469 /* SharedStructAttributeTests.swift */,
226236
220432AE274E7BF800BAE645 /* SharedStructTests.swift */,
237+
2289E82B29A879A7009D89D7 /* SingleRepresentationTypeElisionTests.swift */,
227238
228FE5E62740DB6D00805D9E /* StringTests.swift */,
228239
22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */,
229-
22043292274A8FDF00BAE645 /* VecTests.swift */,
230-
22553323281DB5FC008A3121 /* GenericTests.rs.swift */,
231-
2289E82B29A879A7009D89D7 /* SingleRepresentationTypeElisionTests.swift */,
232240
22EE4E0A28B538A700FEC83C /* SwiftFnUsesOpaqueSwiftTypeTests.swift */,
233-
22C0625428CE6C9A007A6F67 /* CallbackTests.swift */,
234-
C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */,
235-
178F1CD2298E97FB00335AA0 /* ArgumentAttributesTest.swift */,
236241
1745111429BE189B00B96A1A /* TupleTests.swift */,
242+
22043292274A8FDF00BAE645 /* VecTests.swift */,
237243
);
238244
path = SwiftRustIntegrationTestRunnerTests;
239245
sourceTree = "<group>";
@@ -407,6 +413,7 @@
407413
C926E4DE294F07AA0027E7E2 /* FunctionAttributes.swift in Sources */,
408414
228FE64A274919C600805D9E /* swift-integration-tests.swift in Sources */,
409415
22C0625328CE699D007A6F67 /* Callbacks.swift in Sources */,
416+
DE9444DE2D527C3B007A83A4 /* SendableAttribute.swift in Sources */,
410417
22BC10F82799A3A000A0D046 /* SharedStructAttributes.swift in Sources */,
411418
22EE4E0928B5388000FEC83C /* SwiftFnUsesOpaqueSwiftType.swift in Sources */,
412419
228FE60C2740F42000805D9E /* ASwiftStack.swift in Sources */,
@@ -418,12 +425,13 @@
418425
buildActionMask = 2147483647;
419426
files = (
420427
22043293274A8FDF00BAE645 /* VecTests.swift in Sources */,
421-
221E16B62786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift in Sources */,
428+
221E16B62786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift in Sources */,
422429
220432A7274C953E00BAE645 /* PointerTests.swift in Sources */,
423430
C926E4E0294F18C50027E7E2 /* FunctionAttributeTests.swift in Sources */,
424431
220432AF274E7BF800BAE645 /* SharedStructTests.swift in Sources */,
425432
178F1CD3298E97FB00335AA0 /* ArgumentAttributesTest.swift in Sources */,
426433
2289E82C29A879A7009D89D7 /* SingleRepresentationTypeElisionTests.swift in Sources */,
434+
DE9444DC2D5241AD007A83A4 /* SendableAttributeTests.swift in Sources */,
427435
220432EC27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift in Sources */,
428436
222A81EB28EB5DF800D4A412 /* PrimitiveTests.swift in Sources */,
429437
22553324281DB5FC008A3121 /* GenericTests.rs.swift in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// SendableAttribute.swift
3+
// SwiftRustIntegrationTestRunner
4+
//
5+
// Created by Frankie Nwafili on 2/4/25.
6+
//
7+
8+
final class SendableSwiftType {}
9+
extension SendableSwiftType: Sendable {}
10+
11+
final class AnotherSendableSwiftType {}
12+
extension AnotherSendableSwiftType: Sendable {}

‎SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OpaqueTypeAttributeTests.swift ‎SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/AlreadyDeclaredAttributeTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// OpaqueTypeAttributeTests.swift
2+
// AlreadyDeclaredAttributeTests.swift
33
// SwiftRustIntegrationTestRunnerTests
44
//
55
// Created by Frankie Nwafili on 1/6/22.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// SendableAttributeTests.swift
3+
// SwiftRustIntegrationTestRunner
4+
//
5+
// Created by Frankie Nwafili on 2/4/25.
6+
//
7+
8+
import XCTest
9+
@testable import SwiftRustIntegrationTestRunner
10+
11+
/// Tests for the `#[swift_bridge(Sendable)]` attribute.
12+
///
13+
/// For the corresponding Rust code, see `crates/swift-integration-tests/src/sendable_attribute.rs`.
14+
/// For the corresponding codegen tests, see `crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs`.
15+
class SendableAttributeTests: XCTestCase {
16+
/// Verify that we can a Rust type that has the `#[swift_bridge(Sendable)]` attribute gets a `Sendable` protocol implementation.
17+
func testSendableExternRustType() throws {
18+
let sendableRustType = SendableRustType()
19+
20+
// Move the type to another thread.
21+
Thread.detachNewThread {
22+
let _ = sendableRustType;
23+
}
24+
}
25+
}
26+
27+
28+
protocol AssertIsSendable: Sendable {}
29+
extension SendableRustType: AssertIsSendable{}
30+

‎book/src/bridge-module/opaque-types/README.md

+25
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,31 @@ table[val] = "world"
266266
print(table[val])
267267
```
268268

269+
#### #[swift_bridge(Sendable)]
270+
271+
The `Sendable` attribute can be added to both opaque Rust and opaque Swift types.
272+
273+
When applied to an opaque Rust type, the generated Swift type will implement Swift's `Sendable` protocol.
274+
`swift-bridge` will emit code that, at compile time, confirms that the Rust type implements `Send + Sync`.
275+
276+
When applied to an opaque Swift type, the generated Rust type will implement Rust's `Send + Sync` traits.
277+
`swift-bridge` will emit code that, at compile time, confirms that the Swift type implements `Sendable`.
278+
279+
```rust
280+
#[swift_bridge::bridge]
281+
mod ffi {
282+
extern "Rust" {
283+
#[swift_bridge(Sendable)]
284+
type MyRustType;
285+
}
286+
287+
extern "Swift" {
288+
#[swift_bridge(Sendable)]
289+
type MySwiftType;
290+
}
291+
}
292+
```
293+
269294
#### #[swift_bridge(__experimental_ownership)]
270295

271296
The `__experimental_ownership` attribute instructs `swift-bridge` to emit code that takes advantage of Swift 6's

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ mod boxed_fnonce;
3434
mod built_in_tuple;
3535
mod c_header_declaration_order;
3636
mod conditional_compilation;
37-
mod derive_attribute;
38-
mod derive_struct_attribute;
37+
mod derive_copy_clone;
38+
mod derive_debug;
3939
mod extern_rust_function_opaque_rust_type_argument;
4040
mod extern_rust_function_opaque_rust_type_return;
4141
mod extern_rust_method_swift_class_placement;
@@ -46,6 +46,7 @@ mod opaque_swift_type;
4646
mod option;
4747
mod result;
4848
mod return_into_attribute;
49+
mod sendable_attribute;
4950
mod single_representation_type_elision;
5051
mod string;
5152
mod transparent_enum;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use super::{ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode};
2+
use proc_macro2::TokenStream;
3+
use quote::quote;
4+
5+
/// Verify that we can add `#[swift_bridge(Sendable)]` to an extern Rust type.
6+
mod extern_rust_sendable_attribute {
7+
use super::*;
8+
use crate::codegen::codegen_tests::CodegenTest;
9+
10+
fn bridge_module_tokens() -> TokenStream {
11+
quote! {
12+
mod ffi {
13+
extern "Rust" {
14+
#[swift_bridge(Sendable)]
15+
type SomeType;
16+
}
17+
}
18+
}
19+
}
20+
21+
/// Verify that we generate a function that frees the memory behind an opaque pointer to a Rust
22+
/// type.
23+
fn expected_rust_tokens() -> ExpectedRustTokens {
24+
ExpectedRustTokens::Contains(quote! {
25+
const fn __swift_bridge__assert_send_sync<T: Send + Sync>() {}
26+
const _: () = { __swift_bridge__assert_send_sync::<super::SomeType>() };
27+
})
28+
}
29+
30+
fn expected_swift_code() -> ExpectedSwiftCode {
31+
ExpectedSwiftCode::ContainsAfterTrim(
32+
r#"
33+
extension SomeType: @unchecked Sendable {}
34+
"#,
35+
)
36+
}
37+
38+
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
39+
r#"
40+
typedef struct SomeType SomeType;
41+
void __swift_bridge__$SomeType$_free(void* self);
42+
"#,
43+
);
44+
45+
#[test]
46+
fn extern_rust_type() {
47+
CodegenTest {
48+
bridge_module: bridge_module_tokens().into(),
49+
expected_rust_tokens: expected_rust_tokens(),
50+
expected_swift_code: expected_swift_code(),
51+
expected_c_header: EXPECTED_C_HEADER,
52+
}
53+
.test();
54+
}
55+
}
56+
57+
/// Verify that we can add `#[swift_bridge(Sendable)]` to an extern Swift type.
58+
mod extern_swift_sendable_attribute {
59+
use super::*;
60+
use crate::codegen::codegen_tests::CodegenTest;
61+
62+
fn bridge_module_tokens() -> TokenStream {
63+
quote! {
64+
mod foo {
65+
extern "Swift" {
66+
#[swift_bridge(Sendable)]
67+
type SomeSwiftType;
68+
}
69+
}
70+
}
71+
}
72+
73+
fn expected_rust_tokens() -> ExpectedRustTokens {
74+
ExpectedRustTokens::Contains(quote! {
75+
#[repr(C)]
76+
pub struct SomeSwiftType(*mut std::ffi::c_void);
77+
78+
impl Drop for SomeSwiftType {
79+
fn drop (&mut self) {
80+
unsafe { __swift_bridge__SomeSwiftType__free(self.0) }
81+
}
82+
}
83+
84+
unsafe impl Send for SomeSwiftType {}
85+
unsafe impl Sync for SomeSwiftType {}
86+
87+
})
88+
}
89+
90+
const EXPECTED_SWIFT_CODE: ExpectedSwiftCode = ExpectedSwiftCode::ContainsAfterTrim(
91+
r#"
92+
protocol __swift_bridge__IsSendable: Sendable {}
93+
extension SomeSwiftType: __swift_bridge__IsSendable {}
94+
"#,
95+
);
96+
97+
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim(r#""#);
98+
99+
#[test]
100+
fn extern_swift_type_derive_sendable() {
101+
CodegenTest {
102+
bridge_module: bridge_module_tokens().into(),
103+
expected_rust_tokens: expected_rust_tokens(),
104+
expected_swift_code: EXPECTED_SWIFT_CODE,
105+
expected_c_header: EXPECTED_C_HEADER,
106+
}
107+
.test();
108+
}
109+
}

‎crates/swift-bridge-ir/src/codegen/generate_c_header.rs

+6
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,12 @@ fn declare_func(
523523
#[cfg(test)]
524524
mod tests {
525525
//! More tests can be found in src/codegen/codegen_tests.rs and its submodules.
526+
//!
527+
//! TODO: Gradually delete these tests and replace them with tests in the existing
528+
//! `mod codegen_tests`.
529+
//! This way we have one place to analyze the related Rust+Swift+C generated code
530+
//! vs. currently needing to look at `generate_swift.rs` `generate_c.rs` and `generate_rust.rs`
531+
//! to get a full picture of the codegen.
526532
527533
use proc_macro2::TokenStream;
528534
use quote::quote;

‎crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs

+54-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use quote::{quote, quote_spanned};
88

99
use self::vec::vec_of_opaque_rust_type::generate_vec_of_opaque_rust_type_functions;
1010
use crate::bridge_module_attributes::CfgAttr;
11-
use crate::parse::{HostLang, SharedTypeDeclaration, TypeDeclaration};
11+
use crate::parse::{
12+
HostLang, OpaqueForeignTypeDeclaration, SharedTypeDeclaration, TypeDeclaration,
13+
};
1214
use crate::SwiftBridgeModule;
1315

1416
mod shared_enum;
@@ -33,6 +35,8 @@ impl ToTokens for SwiftBridgeModule {
3335
let mut freestanding_rust_call_swift_fn_tokens = vec![];
3436
let mut extern_swift_fn_tokens = vec![];
3537

38+
let mut has_encountered_at_least_one_rust_sendable_type = false;
39+
3640
for func in &self.functions {
3741
match func.host_lang {
3842
HostLang::Rust => {
@@ -215,6 +219,19 @@ impl ToTokens for SwiftBridgeModule {
215219
generate_vec_of_opaque_rust_type_functions(ty_name);
216220
extern_rust_fn_tokens.push(vec_functions);
217221
}
222+
223+
if ty.attributes.sendable {
224+
if !has_encountered_at_least_one_rust_sendable_type {
225+
extern_rust_fn_tokens.push(
226+
generate_extern_rust_type_send_sync_checker()
227+
);
228+
229+
has_encountered_at_least_one_rust_sendable_type = true;
230+
}
231+
232+
extern_rust_fn_tokens
233+
.push(generate_extern_rust_type_send_sync_check(ty));
234+
}
218235
}
219236
}
220237
}
@@ -234,6 +251,15 @@ impl ToTokens for SwiftBridgeModule {
234251
}
235252
};
236253

254+
let maybe_impl_send_sync = if ty.attributes.sendable {
255+
quote! {
256+
unsafe impl Send for #ty_name {}
257+
unsafe impl Sync for #ty_name {}
258+
}
259+
} else {
260+
quote! {}
261+
};
262+
237263
let struct_tokens = quote! {
238264
#[repr(C)]
239265
pub struct #ty_name(*mut std::ffi::c_void);
@@ -245,6 +271,8 @@ impl ToTokens for SwiftBridgeModule {
245271
unsafe { #free_mem_func_name(self.0) }
246272
}
247273
}
274+
275+
#maybe_impl_send_sync
248276
};
249277
structs_for_swift_classes.push(struct_tokens);
250278

@@ -384,9 +412,34 @@ fn generate_extern_c_block(extern_swift_fn_tokens: Vec<TokenStream>) -> TokenStr
384412
}
385413
}
386414

415+
/// Generate a function that can be used to check at compile time that a type implements
416+
/// `Send + Sync`.
417+
fn generate_extern_rust_type_send_sync_checker() -> TokenStream {
418+
quote! {
419+
const fn __swift_bridge__assert_send_sync<T: Send + Sync>() {}
420+
}
421+
}
422+
423+
/// Generate code that checks at compile time that a particular type implements `Send + Sync`.
424+
fn generate_extern_rust_type_send_sync_check(ty: &OpaqueForeignTypeDeclaration) -> TokenStream {
425+
let ty_name = ty.ty_name_ident();
426+
427+
quote! {
428+
const _: () = {
429+
__swift_bridge__assert_send_sync::<super::#ty_name>()
430+
};
431+
}
432+
}
433+
387434
#[cfg(test)]
388435
mod tests {
389436
//! More tests can be found in src/codegen/codegen_tests.rs and its submodules.
437+
//!
438+
//! TODO: Gradually delete these tests and replace them with tests in the existing
439+
//! `mod codegen_tests`.
440+
//! This way we have one place to analyze the related Rust+Swift+C generated code
441+
//! vs. currently needing to look at `generate_swift.rs` `generate_c.rs` and `generate_rust.rs`
442+
//! to get a full picture of the codegen.
390443
391444
use quote::quote;
392445

‎crates/swift-bridge-ir/src/codegen/generate_swift.rs

+73-35
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::collections::HashMap;
2-
32
use syn::Path;
43

54
use crate::bridged_type::{BridgeableType, BridgedType, TypePosition};
@@ -36,6 +35,8 @@ impl SwiftBridgeModule {
3635
HashMap::new();
3736
let mut class_protocols: HashMap<String, ClassProtocols> = HashMap::new();
3837

38+
let mut has_encountered_at_least_one_sendable_swift_type = false;
39+
3940
for function in &self.functions {
4041
if function.host_lang.is_rust() {
4142
if let Some(ty) = function.associated_type.as_ref() {
@@ -102,46 +103,65 @@ impl SwiftBridgeModule {
102103
swift += "\n";
103104
}
104105
}
105-
TypeDeclaration::Opaque(ty) => match ty.host_lang {
106-
HostLang::Rust => {
107-
if let Some(_copy) = ty.attributes.copy {
108-
swift += &generate_opaque_copy_struct(
109-
ty,
110-
&associated_funcs_and_methods,
111-
&self.types,
112-
&self.swift_bridge_path,
113-
);
114-
} else {
115-
let class_protocols = class_protocols.get(&ty.ty.to_string());
116-
let default_cp = ClassProtocols::default();
117-
let class_protocols = class_protocols.unwrap_or(&default_cp);
118-
119-
swift += &generate_swift_class(
120-
ty,
121-
&associated_funcs_and_methods,
122-
class_protocols,
123-
&self.types,
124-
&self.swift_bridge_path,
125-
);
106+
TypeDeclaration::Opaque(ty) => {
107+
match ty.host_lang {
108+
HostLang::Rust => {
109+
if let Some(_copy) = ty.attributes.copy {
110+
swift += &generate_opaque_copy_struct(
111+
ty,
112+
&associated_funcs_and_methods,
113+
&self.types,
114+
&self.swift_bridge_path,
115+
);
116+
} else {
117+
let class_protocols = class_protocols.get(&ty.ty.to_string());
118+
let default_cp = ClassProtocols::default();
119+
let class_protocols = class_protocols.unwrap_or(&default_cp);
120+
121+
swift += &generate_swift_class(
122+
ty,
123+
&associated_funcs_and_methods,
124+
class_protocols,
125+
&self.types,
126+
&self.swift_bridge_path,
127+
);
128+
}
129+
130+
swift += "\n";
131+
132+
if !ty.attributes.already_declared {
133+
// TODO: Support Vec<OpaqueCopyType>. Add codegen tests and then
134+
// make them pass.
135+
// TODO: Support Vec<GenericOpaqueRustType
136+
if ty.attributes.copy.is_none() && ty.generics.len() == 0 {
137+
swift += &generate_vectorizable_extension(&ty);
138+
swift += "\n";
139+
}
140+
}
141+
142+
if ty.attributes.sendable {
143+
let ty_name = ty.ty_name_string();
144+
swift += &format!("extension {ty_name}: @unchecked Sendable {{}}")
145+
}
126146
}
147+
HostLang::Swift => {
148+
swift += &generate_drop_swift_instance_reference_count(ty);
127149

128-
swift += "\n";
150+
if ty.attributes.sendable {
151+
if !has_encountered_at_least_one_sendable_swift_type {
152+
swift += create_swift_sendable_protocol_check();
153+
swift += "\n";
129154

130-
if !ty.attributes.already_declared {
131-
// TODO: Support Vec<OpaqueCopyType>. Add codegen tests and then
132-
// make them pass.
133-
// TODO: Support Vec<GenericOpaqueRustType
134-
if ty.attributes.copy.is_none() && ty.generics.len() == 0 {
135-
swift += &generate_vectorizable_extension(&ty);
136-
swift += "\n";
155+
has_encountered_at_least_one_sendable_swift_type = true;
156+
}
157+
158+
swift += &implement_swift_sendable_protocol(ty);
137159
}
160+
161+
swift += "\n";
138162
}
139163
}
140-
HostLang::Swift => {
141-
swift += &generate_drop_swift_instance_reference_count(ty);
142-
swift += "\n";
143-
}
144-
},
164+
}
145165
};
146166
}
147167

@@ -385,9 +405,27 @@ fn generate_swift_class_methods(
385405
}
386406
}
387407

408+
/// A Swift protocol that inherits from Swift's `Sendable` protocol.
409+
/// We use this to validate at compile time that a Swift type is `Sendable`.
410+
fn create_swift_sendable_protocol_check() -> &'static str {
411+
"protocol __swift_bridge__IsSendable: Sendable {}"
412+
}
413+
414+
/// A Swift protocol that inherits from Swift's `Sendable` protocol.
415+
/// We use this to validate at compile time that a Swift type is `Sendable`.
416+
fn implement_swift_sendable_protocol(ty: &OpaqueForeignTypeDeclaration) -> String {
417+
let ty_name = ty.ty_name_string();
418+
format!("extension {ty_name}: __swift_bridge__IsSendable {{}}")
419+
}
420+
388421
#[cfg(test)]
389422
mod tests {
390423
//! More tests can be found in src/codegen/codegen_tests.rs and its submodules.
424+
//! TODO: Gradually delete these tests and replace them with tests in the existing
425+
//! `mod codegen_tests`.
426+
//! This way we have one place to analyze the related Rust+Swift+C generated code
427+
//! vs. currently needing to look at `generate_swift.rs` `generate_c.rs` and `generate_rust.rs`
428+
//! to get a full picture of the codegen.
391429
392430
use crate::codegen::generate_swift::CodegenConfig;
393431
use quote::quote;

‎crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs

+36
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ pub(crate) struct OpaqueTypeSwiftBridgeAttributes {
3232
/// `#[swift_bridge(Hashable)]`
3333
/// Used to determine if Hashable need to be implemented.
3434
pub hashable: bool,
35+
/// `#[swift_bridge(Sendable)]`
36+
/// Used to determine if the generated type should implement the Swift's `Sendable` protocol or
37+
/// Rust's `Send+Sync` trait.
38+
///
39+
/// When applied to a Rust type the generated Swift code will implement `Sendable`.
40+
/// Compile time checks get emitted to ensure that the Rust type is `Send + Sync`.
41+
///
42+
/// When applied to a Swift type the generated Swift code will implement `Send + Sync`.
43+
/// Compile time checks get emitted to ensure that the Swift type is `Sendable`.
44+
pub sendable: bool,
3545
/// `#[swift_bridge(__experimental_swift_ownership)]`
3646
/// Enables experimental support for Swift ownership.
3747
/// This attribute will eventually be removed once we've stabilized our support for Swift
@@ -83,6 +93,7 @@ impl OpaqueTypeSwiftBridgeAttributes {
8393
OpaqueTypeAttr::DeclareGeneric => self.declare_generic = true,
8494
OpaqueTypeAttr::Equatable => self.equatable = true,
8595
OpaqueTypeAttr::Hashable => self.hashable = true,
96+
OpaqueTypeAttr::Sendable => self.sendable = true,
8697
OpaqueTypeAttr::ExperimentalSwiftOwnership => self.experimental_swift_ownership = true,
8798
}
8899
}
@@ -94,6 +105,7 @@ pub(crate) enum OpaqueTypeAttr {
94105
DeclareGeneric,
95106
Equatable,
96107
Hashable,
108+
Sendable,
97109
ExperimentalSwiftOwnership,
98110
}
99111

@@ -132,6 +144,7 @@ impl Parse for OpaqueTypeAttr {
132144
"declare_generic" => OpaqueTypeAttr::DeclareGeneric,
133145
"Equatable" => OpaqueTypeAttr::Equatable,
134146
"Hashable" => OpaqueTypeAttr::Hashable,
147+
"Sendable" => OpaqueTypeAttr::Sendable,
135148
"__experimental_swift_ownership" => OpaqueTypeAttr::ExperimentalSwiftOwnership,
136149
_ => {
137150
let attrib = key.to_string();
@@ -275,6 +288,29 @@ mod tests {
275288
assert_eq!(attribs.experimental_swift_ownership, true);
276289
}
277290

291+
/// Verify that we parse a Rust or Swift opaque type's `Sendable` attribute.
292+
#[test]
293+
fn parse_sendable_attribute() {
294+
let tokens = quote! {
295+
mod foo {
296+
extern "Rust" {
297+
#[swift_bridge(Sendable)]
298+
type SomeRustType;
299+
}
300+
301+
extern "Swift" {
302+
#[swift_bridge(Sendable)]
303+
type SomeSwiftType;
304+
}
305+
}
306+
};
307+
308+
for ty_name in ["SomeRustType", "SomeSwiftType"] {
309+
let attribs = unwrap_opaque_type_attributes(tokens.clone(), ty_name);
310+
assert_eq!(attribs.sendable, true);
311+
}
312+
}
313+
278314
fn unwrap_opaque_type_attributes(
279315
tokens: TokenStream,
280316
type_name: &'static str,

‎crates/swift-bridge-ir/src/parse/type_declarations.rs

+4
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ impl OpaqueForeignTypeDeclaration {
205205
pub fn ty_name_ident(&self) -> &Ident {
206206
&self.ty
207207
}
208+
209+
pub fn ty_name_string(&self) -> String {
210+
self.ty.to_string()
211+
}
208212
}
209213

210214
impl TypeDeclarations {

‎crates/swift-integration-tests/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod vec;
2222
mod enum_attributes;
2323
mod function_attributes;
2424
mod opaque_type_attributes;
25+
mod sendable_attribute;
2526
mod struct_attributes;
2627

2728
mod futures_experiment;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//! See `crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs` for codegen tests.
2+
3+
#[swift_bridge::bridge]
4+
mod ffi {
5+
extern "Rust" {
6+
#[swift_bridge(Sendable)]
7+
type SendableRustType;
8+
#[swift_bridge(init)]
9+
fn new() -> SendableRustType;
10+
11+
// Verify that our generated code does not emit any duplicate symbols.
12+
//
13+
// We accomplish this by defining this second `Sendable` type.
14+
// If our code compiles then we know there weren't any duplicate codegen.
15+
#[swift_bridge(Sendable)]
16+
type AnotherSendableRustType;
17+
}
18+
19+
extern "Swift" {
20+
#[swift_bridge(Sendable)]
21+
type SendableSwiftType;
22+
23+
// Verify that our generated code does not emit any duplicate symbols.
24+
//
25+
// We accomplish this by defining this second `Sendable` type.
26+
// If our code compiles then we know there weren't any duplicate codegen.
27+
#[swift_bridge(Sendable)]
28+
type AnotherSendableSwiftType;
29+
}
30+
}
31+
32+
struct SendableRustType;
33+
impl SendableRustType {
34+
fn new() -> Self {
35+
Self
36+
}
37+
}
38+
39+
struct AnotherSendableRustType;
40+
41+
const fn assert_send_sync<T: Send + Sync>() {}
42+
43+
// Verify that the `SendableSwiftType` implements `Send + Sync`.
44+
#[allow(unused)]
45+
const TEST_SENDABLE_SWIFT_TYPE_SEND_SYNC: () = {
46+
assert_send_sync::<ffi::SendableSwiftType>();
47+
};

0 commit comments

Comments
 (0)
Please sign in to comment.