Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #[swift_bridge(Sendable)] attribute #317

Merged
merged 1 commit into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -84,7 +86,7 @@
220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustFnUsesOpaqueSwiftTypeTests.swift; sourceTree = "<group>"; };
22046382282B4E3F00A09119 /* FunctionAttributeGetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionAttributeGetTests.swift; sourceTree = "<group>"; };
221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalCompilationTests.swift; sourceTree = "<group>"; };
221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpaqueTypeAttributeTests.swift; sourceTree = "<group>"; };
221E16B52786F9FF00F94AC0 /* AlreadyDeclaredAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlreadyDeclaredAttributeTests.swift; sourceTree = "<group>"; };
222A81E828EB5BB100D4A412 /* Primitive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Primitive.swift; sourceTree = "<group>"; };
222A81EA28EB5DF800D4A412 /* PrimitiveTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimitiveTests.swift; sourceTree = "<group>"; };
22553323281DB5FC008A3121 /* GenericTests.rs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericTests.rs.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -126,6 +128,8 @@
22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftFnUsesOpaqueRustTypeTests.swift; sourceTree = "<group>"; };
C926E4DD294F07AA0027E7E2 /* FunctionAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionAttributes.swift; sourceTree = "<group>"; };
C926E4DF294F18C50027E7E2 /* FunctionAttributeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionAttributeTests.swift; sourceTree = "<group>"; };
DE9444DB2D5241A8007A83A4 /* SendableAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableAttributeTests.swift; sourceTree = "<group>"; };
DE9444DD2D527C31007A83A4 /* SendableAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableAttribute.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 = "<group>";
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// OpaqueTypeAttributeTests.swift
// AlreadyDeclaredAttributeTests.swift
// SwiftRustIntegrationTestRunnerTests
//
// Created by Frankie Nwafili on 1/6/22.
Expand Down
Original file line number Diff line number Diff line change
@@ -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{}

25 changes: 25 additions & 0 deletions book/src/bridge-module/opaque-types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions crates/swift-bridge-ir/src/codegen/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
109 changes: 109 additions & 0 deletions crates/swift-bridge-ir/src/codegen/codegen_tests/sendable_attribute.rs
Original file line number Diff line number Diff line change
@@ -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<T: Send + Sync>() {}
const _: () = { __swift_bridge__assert_send_sync::<super::SomeType>() };
})
}

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();
}
}
6 changes: 6 additions & 0 deletions crates/swift-bridge-ir/src/codegen/generate_c_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading