Skip to content

Commit

Permalink
Make enums support the swift_name attrib (#126)
Browse files Browse the repository at this point in the history
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,
    }
}
```
  • Loading branch information
chinedufn committed Dec 15, 2022
1 parent d921eec commit 819e391
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -101,6 +102,7 @@
22BC10F52799283100A0D046 /* SharedStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedStruct.swift; sourceTree = "<group>"; };
22BC10F72799A3A000A0D046 /* SharedStructAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedStructAttributes.swift; sourceTree = "<group>"; };
22BC4BB9294B8CCD0032B8A8 /* SharedEnumAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedEnumAttributeTests.swift; sourceTree = "<group>"; };
22BC4BBB294BA0EC0032B8A8 /* SharedEnumAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedEnumAttributes.swift; sourceTree = "<group>"; };
22BCAAB827A2607700686A21 /* FunctionAttributeIdentifiableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionAttributeIdentifiableTests.swift; sourceTree = "<group>"; };
22C0625228CE699D007A6F67 /* Callbacks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Callbacks.swift; sourceTree = "<group>"; };
22C0625428CE6C9A007A6F67 /* CallbackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallbackTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -173,6 +175,7 @@
22EE4E0828B5388000FEC83C /* SwiftFnUsesOpaqueSwiftType.swift */,
22C0625228CE699D007A6F67 /* Callbacks.swift */,
225908FD28DA0F9F0080C737 /* Result.swift */,
22BC4BBB294BA0EC0032B8A8 /* SharedEnumAttributes.swift */,
);
path = SwiftRustIntegrationTestRunner;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
16 changes: 13 additions & 3 deletions crates/swift-bridge-ir/src/bridged_type/shared_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,17 +12,22 @@ pub(crate) struct SharedEnum {
pub name: Ident,
pub variants: Vec<EnumVariant>,
pub already_declared: bool,
pub swift_name: Option<LitStr>,
}

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
Expand Down Expand Up @@ -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()
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
2 changes: 1 addition & 1 deletion crates/swift-bridge-ir/src/codegen/generate_c_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
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
/// transparent enum's Vectorizable implementation.
///
/// 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");
Expand All @@ -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 _: () = {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
);
}
Expand Down
33 changes: 32 additions & 1 deletion crates/swift-bridge-ir/src/parse/parse_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -15,6 +15,7 @@ pub(crate) struct SharedEnumDeclarationParser<'a> {
enum EnumAttr {
AlreadyDeclared,
Error(EnumAttrParseError),
SwiftName(LitStr),
}

enum EnumAttrParseError {
Expand All @@ -24,6 +25,7 @@ enum EnumAttrParseError {
#[derive(Default)]
struct EnumAttribs {
already_declared: bool,
swift_name: Option<LitStr>,
}

struct ParsedAttribs(Vec<EnumAttr>);
Expand All @@ -45,6 +47,12 @@ impl Parse for EnumAttr {

let attr = match key.to_string().as_str() {
"already_declared" => EnumAttr::AlreadyDeclared,
"swift_name" => {
input.parse::<Token![=]>()?;

let name = input.parse()?;
EnumAttr::SwiftName(name)
}
_ => {
move_input_cursor_to_next_comma(input);
EnumAttr::Error(EnumAttrParseError::UnrecognizedAttribute(key))
Expand Down Expand Up @@ -76,6 +84,9 @@ impl<'a> SharedEnumDeclarationParser<'a> {
.push(ParseError::EnumUnrecognizedAttribute { attribute });
}
},
EnumAttr::SwiftName(name) => {
attribs.swift_name = Some(name);
}
}
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions crates/swift-integration-tests/src/enum_attributes.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod already_declared;
mod swift_name;
27 changes: 27 additions & 0 deletions crates/swift-integration-tests/src/enum_attributes/swift_name.rs
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 819e391

Please sign in to comment.