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 attribute for Swift ownership support #314

Merged
merged 2 commits into from
Feb 2, 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
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.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests moved to another file

#[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);
}
Comment on lines +262 to +276
Copy link
Owner Author

@chinedufn chinedufn Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only new test case.
The rest were moved from another file.


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
}
}
Loading