Skip to content

Commit

Permalink
Add attribute for Swift ownership support (#314)
Browse files Browse the repository at this point in the history
This commit introduces a new attribute that can be used to enable
experimental support for Swift ownership.

The new `__experimental_swift_ownership` can be used on an opaque Rust
type.

```rust
#[swift_bridge::bridge]
mod foo {
    extern "Rust" {
        #[swift_bridge(__experimental_swift_ownership)]
        type SomeType;
    }
}
```

This new attribute currently does nothing.
In future commits, when `swift-bridge` sees that a type has the
experimental ownership attribute it will generate code that takes
advantage of Swift 6's ownership features.

For instance, we will use Swift's `~Copyable` protocol to ensure that
when Swift has ownership over an opaque Rust type it cannot copy that
handle, which should make attempts to use-after-free a compile-time
error instead of a runtime error.
  • Loading branch information
chinedufn authored Feb 2, 2025
1 parent 225380e commit a661800
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 153 deletions.
29 changes: 28 additions & 1 deletion book/src/bridge-module/opaque-types/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Opaque Types

... TODO OVERVIEW ...
This chapter explains how to expose opaque handles to Swift classes and Rust structs.

## Exposing Opaque Rust Types

Expand Down Expand Up @@ -265,3 +265,30 @@ table[val] = "world"
//Should print "world"
print(table[val])
```

#### #[swift_bridge(__experimental_ownership)]

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

Once `swift-bridge`'s support for Swift's ownership features stabilizes, this attribute will be removed and the behavior
that it enabled will become the default.

When `swift-bridge`'s Swift ownership support is complete, the following will be supported:

- use Swift's `~Copyable` extension to:
- guarantee at compile time that Swift code cannot use a Rust type that it no longer owns
- prevent Swift from automatically copying mutable references to Rust types

Note that support for this attribute is a work in progress.
Work is tracked in `Enforce ownership in generated Swift code` https://github.com/chinedufn/swift-bridge/issues/155 .

```rust
#[swift_bridge::bridge]
mod foo {
extern "Rust" {
#[swift_bridge(__experimental_swift_ownership)]
type SomeType;
}
}
```
152 changes: 0 additions & 152 deletions crates/swift-bridge-ir/src/parse/parse_extern_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,158 +805,6 @@ mod tests {
assert_eq!(parse_errors(tokens).len(), 0,);
}

/// Verify that we can parse the `already_declared` attribute.
#[test]
fn parse_already_declared_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(already_declared)]
type AnotherType;
}
}
};

let module = parse_ok(tokens);

assert!(
module
.types
.get("AnotherType")
.unwrap()
.unwrap_opaque()
.attributes
.already_declared
);
}

//Verify that we can parse the `hashable` attribute.
#[test]
fn parse_hashable_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(Hashable)]
type SomeType;
}
}
};

let module = parse_ok(tokens);

assert_eq!(
module
.types
.get("SomeType")
.unwrap()
.unwrap_opaque()
.attributes
.hashable,
true
);
}

/// Verify that we can parse the `equatable` attribute.
#[test]
fn parse_equatable_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(Equatable)]
type SomeType;
}
}
};

let module = parse_ok(tokens);

assert_eq!(
module
.types
.get("SomeType")
.unwrap()
.unwrap_opaque()
.attributes
.equatable,
true
);
}

/// Verify that we can parse the `copy` attribute.
#[test]
fn parse_copy_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(Copy(4))]
type SomeType;
}
}
};

let module = parse_ok(tokens);

assert_eq!(
module
.types
.get("SomeType")
.unwrap()
.unwrap_opaque()
.attributes
.copy
.unwrap()
.size_bytes,
4
);
}

/// Verify that we can parse multiple atributes from an opaque type.
#[test]
fn parse_multiple_attributes() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(already_declared, Copy(4))]
type SomeType;
}
}
};

let module = parse_ok(tokens);

let ty = module.types.get("SomeType").unwrap().unwrap_opaque();
assert!(ty.attributes.copy.is_some());
assert!(ty.attributes.already_declared)
}

/// Verify that we can parse a doc comment from an extern "Rust" opaque type.
#[test]
fn parse_opaque_rust_type_doc_comment() {
let tokens = quote! {
mod foo {
extern "Rust" {
/// Some comment
type AnotherType;
}
}
};

let module = parse_ok(tokens);

assert_eq!(
module
.types
.get("AnotherType")
.unwrap()
.unwrap_opaque()
.attributes
.doc_comment
.as_ref()
.unwrap(),
" Some comment"
);
}

/// Verify that we push errors for unknown arguments in a function
#[test]
fn error_args_into_arg_not_found_in_function() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ pub(crate) struct OpaqueTypeSwiftBridgeAttributes {
/// `#[swift_bridge(Hashable)]`
/// Used to determine if Hashable need to be implemented.
pub hashable: 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
/// ownership.
/// issue: https://github.com/chinedufn/swift-bridge/issues/155
pub experimental_swift_ownership: bool,
}

impl OpaqueTypeAllAttributes {
Expand Down Expand Up @@ -77,6 +83,7 @@ impl OpaqueTypeSwiftBridgeAttributes {
OpaqueTypeAttr::DeclareGeneric => self.declare_generic = true,
OpaqueTypeAttr::Equatable => self.equatable = true,
OpaqueTypeAttr::Hashable => self.hashable = true,
OpaqueTypeAttr::ExperimentalSwiftOwnership => self.experimental_swift_ownership = true,
}
}
}
Expand All @@ -87,6 +94,7 @@ pub(crate) enum OpaqueTypeAttr {
DeclareGeneric,
Equatable,
Hashable,
ExperimentalSwiftOwnership,
}

impl Parse for OpaqueTypeSwiftBridgeAttributes {
Expand Down Expand Up @@ -124,6 +132,7 @@ impl Parse for OpaqueTypeAttr {
"declare_generic" => OpaqueTypeAttr::DeclareGeneric,
"Equatable" => OpaqueTypeAttr::Equatable,
"Hashable" => OpaqueTypeAttr::Hashable,
"__experimental_swift_ownership" => OpaqueTypeAttr::ExperimentalSwiftOwnership,
_ => {
let attrib = key.to_string();
Err(syn::Error::new_spanned(
Expand All @@ -144,3 +153,139 @@ impl Deref for OpaqueTypeAllAttributes {
&self.swift_bridge
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::parse_ok;
use proc_macro2::TokenStream;
use quote::quote;

/// Verify that we can parse the `already_declared` attribute.
#[test]
fn parse_already_declared_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(already_declared)]
type AnotherType;
}
}
};

let attribs = unwrap_opaque_type_attributes(tokens, "AnotherType");
assert!(attribs.already_declared);
}

/// Verify that we can parse the `hashable` attribute.
#[test]
fn parse_hashable_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(Hashable)]
type SomeType;
}
}
};

let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");
assert_eq!(attribs.hashable, true);
}

/// Verify that we can parse the `equatable` attribute.
#[test]
fn parse_equatable_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(Equatable)]
type SomeType;
}
}
};

let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");
assert_eq!(attribs.equatable, true);
}

/// Verify that we can parse the `copy` attribute.
#[test]
fn parse_copy_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(Copy(4))]
type SomeType;
}
}
};

let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");
assert_eq!(attribs.copy.unwrap().size_bytes, 4);
}

/// Verify that we can parse multiple atributes from an opaque type.
#[test]
fn parse_multiple_attributes() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(already_declared, Copy(4))]
type SomeType;
}
}
};

let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");

assert!(attribs.copy.is_some());
assert!(attribs.already_declared)
}

/// Verify that we can parse a doc comment from an extern "Rust" opaque type.
#[test]
fn parse_opaque_rust_type_doc_comment() {
let tokens = quote! {
mod foo {
extern "Rust" {
/// Some comment
type AnotherType;
}
}
};

let attribs = unwrap_opaque_type_attributes(tokens, "AnotherType");
assert_eq!(attribs.doc_comment.as_ref().unwrap(), " Some comment");
}

/// Verify that we parse a Rust opaque type's experimental Swift ownership attribute.
#[test]
fn parse_experimental_swift_ownership_attribute() {
let tokens = quote! {
mod foo {
extern "Rust" {
#[swift_bridge(__experimental_swift_ownership)]
type SomeType;
}
}
};
let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");

assert_eq!(attribs.experimental_swift_ownership, true);
}

fn unwrap_opaque_type_attributes(
tokens: TokenStream,
type_name: &'static str,
) -> OpaqueTypeAllAttributes {
let module = parse_ok(tokens);
module
.types
.get(type_name)
.unwrap()
.unwrap_opaque()
.clone()
.attributes
}
}

0 comments on commit a661800

Please sign in to comment.