Skip to content

Commit a661800

Browse files
authored
Add attribute for Swift ownership support (#314)
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.
1 parent 225380e commit a661800

File tree

3 files changed

+173
-153
lines changed

3 files changed

+173
-153
lines changed

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

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Opaque Types
22

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

55
## Exposing Opaque Rust Types
66

@@ -265,3 +265,30 @@ table[val] = "world"
265265
//Should print "world"
266266
print(table[val])
267267
```
268+
269+
#### #[swift_bridge(__experimental_ownership)]
270+
271+
The `__experimental_ownership` attribute instructs `swift-bridge` to emit code that takes advantage of Swift 6's
272+
ownership features.
273+
274+
Once `swift-bridge`'s support for Swift's ownership features stabilizes, this attribute will be removed and the behavior
275+
that it enabled will become the default.
276+
277+
When `swift-bridge`'s Swift ownership support is complete, the following will be supported:
278+
279+
- use Swift's `~Copyable` extension to:
280+
- guarantee at compile time that Swift code cannot use a Rust type that it no longer owns
281+
- prevent Swift from automatically copying mutable references to Rust types
282+
283+
Note that support for this attribute is a work in progress.
284+
Work is tracked in `Enforce ownership in generated Swift code` https://github.com/chinedufn/swift-bridge/issues/155 .
285+
286+
```rust
287+
#[swift_bridge::bridge]
288+
mod foo {
289+
extern "Rust" {
290+
#[swift_bridge(__experimental_swift_ownership)]
291+
type SomeType;
292+
}
293+
}
294+
```

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

-152
Original file line numberDiff line numberDiff line change
@@ -805,158 +805,6 @@ mod tests {
805805
assert_eq!(parse_errors(tokens).len(), 0,);
806806
}
807807

808-
/// Verify that we can parse the `already_declared` attribute.
809-
#[test]
810-
fn parse_already_declared_attribute() {
811-
let tokens = quote! {
812-
mod foo {
813-
extern "Rust" {
814-
#[swift_bridge(already_declared)]
815-
type AnotherType;
816-
}
817-
}
818-
};
819-
820-
let module = parse_ok(tokens);
821-
822-
assert!(
823-
module
824-
.types
825-
.get("AnotherType")
826-
.unwrap()
827-
.unwrap_opaque()
828-
.attributes
829-
.already_declared
830-
);
831-
}
832-
833-
//Verify that we can parse the `hashable` attribute.
834-
#[test]
835-
fn parse_hashable_attribute() {
836-
let tokens = quote! {
837-
mod foo {
838-
extern "Rust" {
839-
#[swift_bridge(Hashable)]
840-
type SomeType;
841-
}
842-
}
843-
};
844-
845-
let module = parse_ok(tokens);
846-
847-
assert_eq!(
848-
module
849-
.types
850-
.get("SomeType")
851-
.unwrap()
852-
.unwrap_opaque()
853-
.attributes
854-
.hashable,
855-
true
856-
);
857-
}
858-
859-
/// Verify that we can parse the `equatable` attribute.
860-
#[test]
861-
fn parse_equatable_attribute() {
862-
let tokens = quote! {
863-
mod foo {
864-
extern "Rust" {
865-
#[swift_bridge(Equatable)]
866-
type SomeType;
867-
}
868-
}
869-
};
870-
871-
let module = parse_ok(tokens);
872-
873-
assert_eq!(
874-
module
875-
.types
876-
.get("SomeType")
877-
.unwrap()
878-
.unwrap_opaque()
879-
.attributes
880-
.equatable,
881-
true
882-
);
883-
}
884-
885-
/// Verify that we can parse the `copy` attribute.
886-
#[test]
887-
fn parse_copy_attribute() {
888-
let tokens = quote! {
889-
mod foo {
890-
extern "Rust" {
891-
#[swift_bridge(Copy(4))]
892-
type SomeType;
893-
}
894-
}
895-
};
896-
897-
let module = parse_ok(tokens);
898-
899-
assert_eq!(
900-
module
901-
.types
902-
.get("SomeType")
903-
.unwrap()
904-
.unwrap_opaque()
905-
.attributes
906-
.copy
907-
.unwrap()
908-
.size_bytes,
909-
4
910-
);
911-
}
912-
913-
/// Verify that we can parse multiple atributes from an opaque type.
914-
#[test]
915-
fn parse_multiple_attributes() {
916-
let tokens = quote! {
917-
mod foo {
918-
extern "Rust" {
919-
#[swift_bridge(already_declared, Copy(4))]
920-
type SomeType;
921-
}
922-
}
923-
};
924-
925-
let module = parse_ok(tokens);
926-
927-
let ty = module.types.get("SomeType").unwrap().unwrap_opaque();
928-
assert!(ty.attributes.copy.is_some());
929-
assert!(ty.attributes.already_declared)
930-
}
931-
932-
/// Verify that we can parse a doc comment from an extern "Rust" opaque type.
933-
#[test]
934-
fn parse_opaque_rust_type_doc_comment() {
935-
let tokens = quote! {
936-
mod foo {
937-
extern "Rust" {
938-
/// Some comment
939-
type AnotherType;
940-
}
941-
}
942-
};
943-
944-
let module = parse_ok(tokens);
945-
946-
assert_eq!(
947-
module
948-
.types
949-
.get("AnotherType")
950-
.unwrap()
951-
.unwrap_opaque()
952-
.attributes
953-
.doc_comment
954-
.as_ref()
955-
.unwrap(),
956-
" Some comment"
957-
);
958-
}
959-
960808
/// Verify that we push errors for unknown arguments in a function
961809
#[test]
962810
fn error_args_into_arg_not_found_in_function() {

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

+145
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ 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(__experimental_swift_ownership)]`
36+
/// Enables experimental support for Swift ownership.
37+
/// This attribute will eventually be removed once we've stabilized our support for Swift
38+
/// ownership.
39+
/// issue: https://github.com/chinedufn/swift-bridge/issues/155
40+
pub experimental_swift_ownership: bool,
3541
}
3642

3743
impl OpaqueTypeAllAttributes {
@@ -77,6 +83,7 @@ impl OpaqueTypeSwiftBridgeAttributes {
7783
OpaqueTypeAttr::DeclareGeneric => self.declare_generic = true,
7884
OpaqueTypeAttr::Equatable => self.equatable = true,
7985
OpaqueTypeAttr::Hashable => self.hashable = true,
86+
OpaqueTypeAttr::ExperimentalSwiftOwnership => self.experimental_swift_ownership = true,
8087
}
8188
}
8289
}
@@ -87,6 +94,7 @@ pub(crate) enum OpaqueTypeAttr {
8794
DeclareGeneric,
8895
Equatable,
8996
Hashable,
97+
ExperimentalSwiftOwnership,
9098
}
9199

92100
impl Parse for OpaqueTypeSwiftBridgeAttributes {
@@ -124,6 +132,7 @@ impl Parse for OpaqueTypeAttr {
124132
"declare_generic" => OpaqueTypeAttr::DeclareGeneric,
125133
"Equatable" => OpaqueTypeAttr::Equatable,
126134
"Hashable" => OpaqueTypeAttr::Hashable,
135+
"__experimental_swift_ownership" => OpaqueTypeAttr::ExperimentalSwiftOwnership,
127136
_ => {
128137
let attrib = key.to_string();
129138
Err(syn::Error::new_spanned(
@@ -144,3 +153,139 @@ impl Deref for OpaqueTypeAllAttributes {
144153
&self.swift_bridge
145154
}
146155
}
156+
157+
#[cfg(test)]
158+
mod tests {
159+
use super::*;
160+
use crate::test_utils::parse_ok;
161+
use proc_macro2::TokenStream;
162+
use quote::quote;
163+
164+
/// Verify that we can parse the `already_declared` attribute.
165+
#[test]
166+
fn parse_already_declared_attribute() {
167+
let tokens = quote! {
168+
mod foo {
169+
extern "Rust" {
170+
#[swift_bridge(already_declared)]
171+
type AnotherType;
172+
}
173+
}
174+
};
175+
176+
let attribs = unwrap_opaque_type_attributes(tokens, "AnotherType");
177+
assert!(attribs.already_declared);
178+
}
179+
180+
/// Verify that we can parse the `hashable` attribute.
181+
#[test]
182+
fn parse_hashable_attribute() {
183+
let tokens = quote! {
184+
mod foo {
185+
extern "Rust" {
186+
#[swift_bridge(Hashable)]
187+
type SomeType;
188+
}
189+
}
190+
};
191+
192+
let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");
193+
assert_eq!(attribs.hashable, true);
194+
}
195+
196+
/// Verify that we can parse the `equatable` attribute.
197+
#[test]
198+
fn parse_equatable_attribute() {
199+
let tokens = quote! {
200+
mod foo {
201+
extern "Rust" {
202+
#[swift_bridge(Equatable)]
203+
type SomeType;
204+
}
205+
}
206+
};
207+
208+
let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");
209+
assert_eq!(attribs.equatable, true);
210+
}
211+
212+
/// Verify that we can parse the `copy` attribute.
213+
#[test]
214+
fn parse_copy_attribute() {
215+
let tokens = quote! {
216+
mod foo {
217+
extern "Rust" {
218+
#[swift_bridge(Copy(4))]
219+
type SomeType;
220+
}
221+
}
222+
};
223+
224+
let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");
225+
assert_eq!(attribs.copy.unwrap().size_bytes, 4);
226+
}
227+
228+
/// Verify that we can parse multiple atributes from an opaque type.
229+
#[test]
230+
fn parse_multiple_attributes() {
231+
let tokens = quote! {
232+
mod foo {
233+
extern "Rust" {
234+
#[swift_bridge(already_declared, Copy(4))]
235+
type SomeType;
236+
}
237+
}
238+
};
239+
240+
let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");
241+
242+
assert!(attribs.copy.is_some());
243+
assert!(attribs.already_declared)
244+
}
245+
246+
/// Verify that we can parse a doc comment from an extern "Rust" opaque type.
247+
#[test]
248+
fn parse_opaque_rust_type_doc_comment() {
249+
let tokens = quote! {
250+
mod foo {
251+
extern "Rust" {
252+
/// Some comment
253+
type AnotherType;
254+
}
255+
}
256+
};
257+
258+
let attribs = unwrap_opaque_type_attributes(tokens, "AnotherType");
259+
assert_eq!(attribs.doc_comment.as_ref().unwrap(), " Some comment");
260+
}
261+
262+
/// Verify that we parse a Rust opaque type's experimental Swift ownership attribute.
263+
#[test]
264+
fn parse_experimental_swift_ownership_attribute() {
265+
let tokens = quote! {
266+
mod foo {
267+
extern "Rust" {
268+
#[swift_bridge(__experimental_swift_ownership)]
269+
type SomeType;
270+
}
271+
}
272+
};
273+
let attribs = unwrap_opaque_type_attributes(tokens, "SomeType");
274+
275+
assert_eq!(attribs.experimental_swift_ownership, true);
276+
}
277+
278+
fn unwrap_opaque_type_attributes(
279+
tokens: TokenStream,
280+
type_name: &'static str,
281+
) -> OpaqueTypeAllAttributes {
282+
let module = parse_ok(tokens);
283+
module
284+
.types
285+
.get(type_name)
286+
.unwrap()
287+
.unwrap_opaque()
288+
.clone()
289+
.attributes
290+
}
291+
}

0 commit comments

Comments
 (0)