From 819e39160ec5b38dce2e60e2d39d078617ad158d Mon Sep 17 00:00:00 2001 From: Chinedu Francis Nwafili Date: Thu, 15 Dec 2022 15:03:52 -0500 Subject: [PATCH] Make enums support the `swift_name` attrib (#126) This commit makes enums support the `swift_name` attribute, which lets you choose the name that shows up on the Swift side. For example, the following is now possible: ```rust #[swift_bridge::bridge] mod ffi { #[swift_bridge(swift_name = "EnumRename")] enum EnumName { Variant1, Variant2, } } ``` --- .../project.pbxproj | 4 ++ .../SharedEnumAttributes.swift | 12 +++++ .../SharedEnumAttributeTests.swift | 11 +++++ .../src/bridged_type/shared_enum.rs | 16 +++++-- .../transparent_enum_codegen_tests.rs | 47 +++++++++++++++++++ .../src/codegen/generate_c_header.rs | 2 +- .../generate_rust_tokens/shared_enum.rs | 2 +- .../vec/vec_of_transparent_enum.rs | 32 +++++++++---- .../swift-bridge-ir/src/parse/parse_enum.rs | 33 ++++++++++++- .../src/enum_attributes.rs | 1 + .../src/enum_attributes/swift_name.rs | 27 +++++++++++ 11 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SharedEnumAttributes.swift create mode 100644 crates/swift-integration-tests/src/enum_attributes/swift_name.rs diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj index 47a5b874..9c30aaf7 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 22BC10F62799283100A0D046 /* SharedStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22BC10F52799283100A0D046 /* SharedStruct.swift */; }; 22BC10F82799A3A000A0D046 /* SharedStructAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22BC10F72799A3A000A0D046 /* SharedStructAttributes.swift */; }; 22BC4BBA294B8CCD0032B8A8 /* SharedEnumAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22BC4BB9294B8CCD0032B8A8 /* SharedEnumAttributeTests.swift */; }; + 22BC4BBC294BA0EC0032B8A8 /* SharedEnumAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22BC4BBB294BA0EC0032B8A8 /* SharedEnumAttributes.swift */; }; 22BCAAB927A2607700686A21 /* FunctionAttributeIdentifiableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22BCAAB827A2607700686A21 /* FunctionAttributeIdentifiableTests.swift */; }; 22C0625328CE699D007A6F67 /* Callbacks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C0625228CE699D007A6F67 /* Callbacks.swift */; }; 22C0625528CE6C9A007A6F67 /* CallbackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C0625428CE6C9A007A6F67 /* CallbackTests.swift */; }; @@ -101,6 +102,7 @@ 22BC10F52799283100A0D046 /* SharedStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedStruct.swift; sourceTree = ""; }; 22BC10F72799A3A000A0D046 /* SharedStructAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedStructAttributes.swift; sourceTree = ""; }; 22BC4BB9294B8CCD0032B8A8 /* SharedEnumAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedEnumAttributeTests.swift; sourceTree = ""; }; + 22BC4BBB294BA0EC0032B8A8 /* SharedEnumAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedEnumAttributes.swift; sourceTree = ""; }; 22BCAAB827A2607700686A21 /* FunctionAttributeIdentifiableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionAttributeIdentifiableTests.swift; sourceTree = ""; }; 22C0625228CE699D007A6F67 /* Callbacks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Callbacks.swift; sourceTree = ""; }; 22C0625428CE6C9A007A6F67 /* CallbackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallbackTests.swift; sourceTree = ""; }; @@ -173,6 +175,7 @@ 22EE4E0828B5388000FEC83C /* SwiftFnUsesOpaqueSwiftType.swift */, 22C0625228CE699D007A6F67 /* Callbacks.swift */, 225908FD28DA0F9F0080C737 /* Result.swift */, + 22BC4BBB294BA0EC0032B8A8 /* SharedEnumAttributes.swift */, ); path = SwiftRustIntegrationTestRunner; sourceTree = ""; @@ -370,6 +373,7 @@ 226F944B27BF79B400243D86 /* String.swift in Sources */, 22043297274B0AB000BAE645 /* Option.swift in Sources */, 220432EA2753092C00BAE645 /* RustFnUsesOpaqueSwiftType.swift in Sources */, + 22BC4BBC294BA0EC0032B8A8 /* SharedEnumAttributes.swift in Sources */, 22FD1C542753CB2A00F64281 /* SwiftFnUsesOpaqueRustType.swift in Sources */, 220432A9274D31DC00BAE645 /* Pointer.swift in Sources */, 225908FE28DA0F9F0080C737 /* Result.swift in Sources */, diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SharedEnumAttributes.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SharedEnumAttributes.swift new file mode 100644 index 00000000..55558803 --- /dev/null +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/SharedEnumAttributes.swift @@ -0,0 +1,12 @@ +// +// SharedEnumAttributes.swift +// SwiftRustIntegrationTestRunner +// +// Created by Frankie Nwafili on 12/15/22. +// + +import Foundation + +func extern_swift_enum_rename(arg: EnumRename) -> EnumRename { + arg +} diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedEnumAttributeTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedEnumAttributeTests.swift index e2da7afb..22f55fb1 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedEnumAttributeTests.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedEnumAttributeTests.swift @@ -10,6 +10,17 @@ import XCTest /// Tests for attributes on transparent enum types. class SharedEnumAttributeTests: XCTestCase { + /// Verify that we change the Swift name of a transparent enum. + func testSharedEnumSwiftName() throws { + XCTAssertEqual( + extern_rust_enum_rename( + EnumRename.Variant1 + ), + EnumRename.Variant1 + ) + } + + /// Verify that we can call a function that uses a type that was already declared in a different bridge module. func testSharedEnumAlreadyDeclared() throws { XCTAssertEqual( diff --git a/crates/swift-bridge-ir/src/bridged_type/shared_enum.rs b/crates/swift-bridge-ir/src/bridged_type/shared_enum.rs index da3a5a60..0e6d62f5 100644 --- a/crates/swift-bridge-ir/src/bridged_type/shared_enum.rs +++ b/crates/swift-bridge-ir/src/bridged_type/shared_enum.rs @@ -2,6 +2,7 @@ use crate::SWIFT_BRIDGE_PREFIX; use proc_macro2::{Ident, TokenStream}; use quote::quote; use std::fmt::{Debug, Formatter}; +use syn::LitStr; mod enum_variant; pub(crate) use self::enum_variant::EnumVariant; @@ -11,17 +12,22 @@ pub(crate) struct SharedEnum { pub name: Ident, pub variants: Vec, pub already_declared: bool, + pub swift_name: Option, } impl SharedEnum { /// SomeEnum pub fn swift_name_string(&self) -> String { - format!("{}", self.name) + if let Some(swift_name) = self.swift_name.as_ref() { + swift_name.value().to_string() + } else { + format!("{}", self.name) + } } /// __swift_bridge__$SomeEnum pub fn ffi_name_string(&self) -> String { - format!("{}${}", SWIFT_BRIDGE_PREFIX, self.name) + format!("{}${}", SWIFT_BRIDGE_PREFIX, self.swift_name_string()) } /// __swift_bridge__$SomeEnumTag @@ -49,7 +55,11 @@ impl SharedEnum { /// __swift_bridge__$Option$SomeEnum pub fn ffi_option_name_string(&self) -> String { - format!("{}$Option${}", SWIFT_BRIDGE_PREFIX, self.name) + format!( + "{}$Option${}", + SWIFT_BRIDGE_PREFIX, + self.swift_name_string() + ) } } diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_enum_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_enum_codegen_tests.rs index 80326132..64e79771 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_enum_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_enum_codegen_tests.rs @@ -299,3 +299,50 @@ struct __swift_bridge__$Option$SomeEnum __swift_bridge__$some_function(struct __ .test(); } } + +/// Verify that the original name of the enum is not present in any of the generated Swift +/// code when we use the `swift_name` attribute.. +/// Related: crates/swift-integration-tests/src/enum_attributes/swift_name.rs +mod shared_enum_swift_name_attribute { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + #[swift_bridge::bridge] + mod ffi { + #[swift_bridge(swift_name = "EnumRename")] + enum EnumName { + Variant + } + + + extern "Rust" { + fn extern_rust_enum_rename(arg: EnumName) -> EnumName; + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::SkipTest + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::DoesNotContainAfterTrim("EnumName") + } + + fn expected_c_header() -> ExpectedCHeader { + ExpectedCHeader::DoesNotContainAfterTrim("EnumName") + } + + #[test] + fn shared_enum_swift_name_attribute() { + 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(); + } +} diff --git a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs index e2e8c28c..f79aa7b9 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs @@ -148,7 +148,7 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi let maybe_vec_support = if ty_enum.has_one_or_more_variants_with_data() { "".to_string() } else { - vec_transparent_enum_c_support(&ty_enum.name.to_string()) + vec_transparent_enum_c_support(&ty_enum.swift_name_string()) }; let enum_decl = format!( diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_enum.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_enum.rs index 68aff638..75792421 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_enum.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_enum.rs @@ -76,7 +76,7 @@ impl SwiftBridgeModule { // Enums with variants that contain data are not yet supported. quote! {} } else { - generate_vec_of_transparent_enum_functions(&shared_enum.name) + generate_vec_of_transparent_enum_functions(&shared_enum) }; let definition = quote! { diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_enum.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_enum.rs index 7bb2da87..6b567dc7 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_enum.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_enum.rs @@ -1,4 +1,5 @@ -use proc_macro2::{Ident, TokenStream}; +use crate::bridged_type::SharedEnum; +use proc_macro2::TokenStream; use quote::quote; /// Generate the functions that Swift calls uses inside of the corresponding class for a @@ -6,12 +7,20 @@ use quote::quote; /// /// So inside of `extension SomeTransparentEnum: Vectorizable {}` on the Swift side. pub(in super::super) fn generate_vec_of_transparent_enum_functions( - enum_name: &Ident, + shared_enum: &SharedEnum, ) -> TokenStream { + let enum_name = &shared_enum.name; + // examples: // "__swift_bridge__$Vec_SomeTransparentEnum$new" // "__swift_bridge__$Vec_SomeTransparentEnum$drop" - let make_export_name = |fn_name| format!("__swift_bridge__$Vec_{}${}", enum_name, fn_name); + let make_export_name = |fn_name| { + format!( + "__swift_bridge__$Vec_{}${}", + shared_enum.swift_name_string(), + fn_name + ) + }; let export_name_new = make_export_name("new"); let export_name_drop = make_export_name("drop"); let export_name_len = make_export_name("len"); @@ -21,11 +30,8 @@ pub(in super::super) fn generate_vec_of_transparent_enum_functions( let export_name_pop = make_export_name("pop"); let export_name_as_ptr = make_export_name("as_ptr"); - let ffi_enum_repr = Ident::new(&format!("__swift_bridge__{}", enum_name), enum_name.span()); - let ffi_option_enum_repr = Ident::new( - &format!("__swift_bridge__Option_{}", enum_name), - enum_name.span(), - ); + let ffi_enum_repr = &shared_enum.ffi_name_tokens(); + let ffi_option_enum_repr = shared_enum.ffi_option_name_tokens(); quote! { const _: () = { @@ -91,7 +97,7 @@ pub(in super::super) fn generate_vec_of_transparent_enum_functions( mod tests { use super::*; use crate::test_utils::assert_tokens_eq; - use proc_macro2::Span; + use proc_macro2::{Ident, Span}; /// Verify that we can generate the functions for an opaque Rust type that get exposed to Swift /// in order to power the `extension MyRustType: Vectorizable { }` implementation on the Swift @@ -157,8 +163,14 @@ mod tests { }; }; + let shared_enum = SharedEnum { + name: Ident::new("AnEnum", Span::call_site()), + variants: vec![], + already_declared: false, + swift_name: None, + }; assert_tokens_eq( - &generate_vec_of_transparent_enum_functions(&Ident::new("AnEnum", Span::call_site())), + &generate_vec_of_transparent_enum_functions(&shared_enum), &expected, ); } diff --git a/crates/swift-bridge-ir/src/parse/parse_enum.rs b/crates/swift-bridge-ir/src/parse/parse_enum.rs index e1c0a831..83a3c1bb 100644 --- a/crates/swift-bridge-ir/src/parse/parse_enum.rs +++ b/crates/swift-bridge-ir/src/parse/parse_enum.rs @@ -3,7 +3,7 @@ use crate::errors::{ParseError, ParseErrors}; use crate::parse::move_input_cursor_to_next_comma; use proc_macro2::Ident; use syn::parse::{Parse, ParseStream}; -use syn::ItemEnum; +use syn::{ItemEnum, LitStr, Token}; pub(crate) struct SharedEnumDeclarationParser<'a> { pub item_enum: ItemEnum, @@ -15,6 +15,7 @@ pub(crate) struct SharedEnumDeclarationParser<'a> { enum EnumAttr { AlreadyDeclared, Error(EnumAttrParseError), + SwiftName(LitStr), } enum EnumAttrParseError { @@ -24,6 +25,7 @@ enum EnumAttrParseError { #[derive(Default)] struct EnumAttribs { already_declared: bool, + swift_name: Option, } struct ParsedAttribs(Vec); @@ -45,6 +47,12 @@ impl Parse for EnumAttr { let attr = match key.to_string().as_str() { "already_declared" => EnumAttr::AlreadyDeclared, + "swift_name" => { + input.parse::()?; + + let name = input.parse()?; + EnumAttr::SwiftName(name) + } _ => { move_input_cursor_to_next_comma(input); EnumAttr::Error(EnumAttrParseError::UnrecognizedAttribute(key)) @@ -76,6 +84,9 @@ impl<'a> SharedEnumDeclarationParser<'a> { .push(ParseError::EnumUnrecognizedAttribute { attribute }); } }, + EnumAttr::SwiftName(name) => { + attribs.swift_name = Some(name); + } } } } @@ -92,6 +103,7 @@ impl<'a> SharedEnumDeclarationParser<'a> { name: item_enum.ident, variants, already_declared: attribs.already_declared, + swift_name: attribs.swift_name, }; Ok(shared_enum) @@ -173,6 +185,25 @@ mod tests { } } + /// Verify that we can parse the `#[swift_bridge(swift_name = "...")`] attribute. + #[test] + fn swift_name_attribute() { + let tokens = quote! { + #[swift_bridge::bridge] + mod ffi { + #[swift_bridge(swift_name = "FfiFoo")] + enum Foo { + Variant1 + } + } + }; + + let module = parse_ok(tokens); + + let ty = module.types.types()[0].unwrap_shared_enum(); + assert_eq!(ty.swift_name.as_ref().unwrap().value(), "FfiFoo"); + } + /// Verify that we can parse the `#[swift_bridge(already_declared)`] attribute. #[test] fn already_declared_attribute() { diff --git a/crates/swift-integration-tests/src/enum_attributes.rs b/crates/swift-integration-tests/src/enum_attributes.rs index 8adf9e7c..6fc2cf4d 100644 --- a/crates/swift-integration-tests/src/enum_attributes.rs +++ b/crates/swift-integration-tests/src/enum_attributes.rs @@ -1 +1,2 @@ mod already_declared; +mod swift_name; diff --git a/crates/swift-integration-tests/src/enum_attributes/swift_name.rs b/crates/swift-integration-tests/src/enum_attributes/swift_name.rs new file mode 100644 index 00000000..5cec03f4 --- /dev/null +++ b/crates/swift-integration-tests/src/enum_attributes/swift_name.rs @@ -0,0 +1,27 @@ +/// We declare an enum and rename it using the `swift_name` attribute. +/// We then use them as function arg and return types. +/// +/// Related: crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_enum_codegen_tests.rs +/// - shared_enum_swift_name_attribute +#[swift_bridge::bridge] +mod ffi { + #[swift_bridge(swift_name = "EnumRename")] + enum EnumName { + Variant1, + Variant2, + } + + extern "Rust" { + fn extern_rust_enum_rename(arg: EnumName) -> EnumName; + } + + extern "Swift" { + fn extern_swift_enum_rename(arg: EnumName) -> EnumName; + } +} + +use ffi::EnumName; + +fn extern_rust_enum_rename(arg: EnumName) -> EnumName { + arg +}