diff --git a/book/src/bridge-module/opaque-types/README.md b/book/src/bridge-module/opaque-types/README.md index 03a2f014..05b839af 100644 --- a/book/src/bridge-module/opaque-types/README.md +++ b/book/src/bridge-module/opaque-types/README.md @@ -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 @@ -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; + } +} +``` diff --git a/crates/swift-bridge-ir/src/parse/parse_extern_mod.rs b/crates/swift-bridge-ir/src/parse/parse_extern_mod.rs index 37a1a84e..664238aa 100644 --- a/crates/swift-bridge-ir/src/parse/parse_extern_mod.rs +++ b/crates/swift-bridge-ir/src/parse/parse_extern_mod.rs @@ -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() { diff --git a/crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs b/crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs index 4f3319a8..870a63c3 100644 --- a/crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs +++ b/crates/swift-bridge-ir/src/parse/parse_extern_mod/opaque_type_attributes.rs @@ -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 { @@ -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, } } } @@ -87,6 +94,7 @@ pub(crate) enum OpaqueTypeAttr { DeclareGeneric, Equatable, Hashable, + ExperimentalSwiftOwnership, } impl Parse for OpaqueTypeSwiftBridgeAttributes { @@ -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( @@ -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 + } +}