Skip to content

Commit

Permalink
Support Vec<TransparentEnum> (#117)
Browse files Browse the repository at this point in the history
This commit adds support for bridging `Vec<TransparentEnum>`.

For example, the following is now possible:

```rust
#[swift_bridge::bridge]
mod ffi {
    enum SomeEnum {
        VariantA,
        VariantB,
    }

    extern "Rust" {
        fn some_function(arg: Vec<SomeEnum>) -> Vec<SomeEnum>;
    }
}
```
  • Loading branch information
chinedufn committed Oct 28, 2022
1 parent 59bff34 commit c1fd717
Show file tree
Hide file tree
Showing 16 changed files with 752 additions and 200 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ In addition to allowing you to share your own custom structs, enums and classes
| &[T] | | Not yet implemented |
| &mut [T] | | Not yet implemented |
| Box<T> | | Not yet implemented |
| Box<dyn FnOnce(A,B,C)> -> D> | (A, B, C) -> D | Passing from Rust to Swift is supported, but Swift to Rust is not yet implemented. |
| Box<dyn Fn(A,B,C)> -> D> | (A, B, C) -> D | Not yet implemented |
| Box<dyn FnOnce(A,B,C) -> D> | (A, B, C) -> D | Passing from Rust to Swift is supported, but Swift to Rust is not yet implemented. |
| Box<dyn Fn(A,B,C) -> D> | (A, B, C) -> D | Not yet implemented |
| [T; N] | | Not yet implemented |
| *const T | UnsafePointer\<T> | |
| *mut T | UnsafeMutablePointer\<T> | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class VecTests: XCTestCase {
XCTAssertEqual(popped?.text().toString(), "hello world")
XCTAssertEqual(vec.len(), 0)
}

/// Verify that a Vec<T> of opaque Rust types can be used as an argument and return
/// type for extern "Rust" functions.
func testReflectVecOfOpaqueRustType() throws {
Expand All @@ -80,6 +81,18 @@ class VecTests: XCTestCase {
XCTAssertEqual(reflected.get(index: 0)!.text().toString(), "hello world")
}

/// Verify that a Vec<T> of transparent enums can be used as an argument and return
/// type for extern "Rust" functions.
func testReflectVecOfTransparentEnum() throws {
let vec: RustVec<TransparentEnumInsideVecT> = RustVec()
vec.push(value: TransparentEnumInsideVecT.VariantB)

let reflected = rust_reflect_vec_transparent_enum(vec)
XCTAssertEqual(reflected.len(), 1)
XCTAssertEqual(reflected.get(index: 0)!, TransparentEnumInsideVecT.VariantB)
XCTAssertEqual(reflected.pop()!, TransparentEnumInsideVecT.VariantB)
}

/// Verify that we can construct a RustVec of every primitive type.
/// We tested all of the methods on two different primitives above to be sure that our
/// functions that generate the pieces of the RustVec support aren't accidentally hard coded to
Expand Down
8 changes: 5 additions & 3 deletions crates/swift-bridge-ir/src/bridged_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,9 +767,11 @@ impl BridgedType {
#ty_name
}
}
BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Enum(_shared_enum))) => {
//
todo!("Shared enum to Rust type name")
BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Enum(shared_enum))) => {
let enum_name = &shared_enum.name;
quote! {
#enum_name
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions crates/swift-bridge-ir/src/bridged_type/shared_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ impl SharedEnum {
}
}

impl SharedEnum {
/// Whether or not any of the enum's variants contain data.
///
/// `EnumWithData { VariantA(u8), VariantB }` -> true
/// `EnumWithData { VariantA(u8), VariantB(u16) }` -> true
/// `EnumWithNoData { VariantA, VariantB }` -> false
pub fn has_one_or_more_variants_with_data(&self) -> bool {
self.variants.iter().any(|v| !v.fields.is_empty())
}
}

impl PartialEq for SharedEnum {
fn eq(&self, other: &Self) -> bool {
self.name.to_string() == other.name.to_string() && self.variants == other.variants
Expand Down
4 changes: 2 additions & 2 deletions crates/swift-bridge-ir/src/codegen/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ mod opaque_rust_type_codegen_tests;
mod opaque_swift_type_codegen_tests;
mod option_codegen_tests;
mod result_codegen_tests;
mod shared_enum_codegen_tests;
mod shared_struct_codegen_tests;
mod string_codegen_tests;
mod transparent_enum_codegen_tests;
mod transparent_struct_codegen_tests;
mod vec_codegen_tests;

struct CodegenTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod generates_enum_to_and_from_ffi_conversions_no_data {

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[derive(Copy, Clone)]
pub enum SomeEnum {
Variant1,
Variant2
Expand Down Expand Up @@ -95,7 +96,7 @@ extension __swift_bridge__$SomeEnum {
}

fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ExactAfterTrim(
ExpectedCHeader::ContainsAfterTrim(
r#"
#include <stdbool.h>
typedef enum __swift_bridge__$SomeEnumTag { __swift_bridge__$SomeEnum$Variant1, __swift_bridge__$SomeEnum$Variant2, } __swift_bridge__$SomeEnumTag;
Expand Down Expand Up @@ -274,15 +275,17 @@ func some_function(_ arg: Optional<SomeEnum>) -> Optional<SomeEnum> {
}

fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ExactAfterTrim(
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
#include <stdbool.h>
typedef enum __swift_bridge__$SomeEnumTag { __swift_bridge__$SomeEnum$Variant1, __swift_bridge__$SomeEnum$Variant2, } __swift_bridge__$SomeEnumTag;
typedef struct __swift_bridge__$SomeEnum { __swift_bridge__$SomeEnumTag tag; } __swift_bridge__$SomeEnum;
typedef struct __swift_bridge__$Option$SomeEnum { bool is_some; __swift_bridge__$SomeEnum val; } __swift_bridge__$Option$SomeEnum;
"#,
r#"
struct __swift_bridge__$Option$SomeEnum __swift_bridge__$some_function(struct __swift_bridge__$Option$SomeEnum arg);
"#,
)
])
}

#[test]
Expand Down
262 changes: 260 additions & 2 deletions crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ void* __swift_bridge__$Vec_MyRustType$as_ptr(void* vec_ptr);
}

#[test]
fn extern_rust_fn_return_vec_of_opaque_rust_type() {
fn extern_rust_type_vec_support() {
CodegenTest {
bridge_module: bridge_module_tokens().into(),
expected_rust_tokens: expected_rust_tokens(),
Expand Down Expand Up @@ -268,7 +268,265 @@ void __swift_bridge__$some_function(void* arg);
}

#[test]
fn extern_rust_fn_return_vec_of_opaque_rust_type() {
fn extern_rust_fn_arg_vec_of_opaque_rust_type() {
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();
}
}

/// Verify that we emit Rust, Swift and C header code that allows a transparent enum be used
/// within a Vec<T>.
mod transparent_enum_vec_support {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeEnum {
VariantA,
VariantB
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
const _: () = {
#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_SomeEnum$new"]
pub extern "C" fn _new() -> *mut Vec<SomeEnum> {
Box::into_raw(Box::new(Vec::new()))
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_SomeEnum$drop"]
pub extern "C" fn _drop(vec: *mut Vec<SomeEnum>) {
let vec = unsafe { Box::from_raw(vec) };
drop(vec)
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_SomeEnum$len"]
pub extern "C" fn _len(vec: *const Vec<SomeEnum>) -> usize {
unsafe { &*vec }.len()
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_SomeEnum$get"]
pub extern "C" fn _get(vec: *const Vec<SomeEnum>, index: usize) -> __swift_bridge__Option_SomeEnum {
let vec = unsafe { &*vec };
let val = vec.get(index).map(|v| *v);
__swift_bridge__Option_SomeEnum::from_rust_repr(val)
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_SomeEnum$get_mut"]
pub extern "C" fn _get_mut(vec: *mut Vec<SomeEnum>, index: usize) -> __swift_bridge__Option_SomeEnum {
let vec = unsafe { &mut *vec };
let val = vec.get_mut(index).map(|v| *v);
__swift_bridge__Option_SomeEnum::from_rust_repr(val)
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_SomeEnum$push"]
pub extern "C" fn _push(vec: *mut Vec<SomeEnum>, val: __swift_bridge__SomeEnum) {
unsafe { &mut *vec }.push(val.into_rust_repr())
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_SomeEnum$pop"]
pub extern "C" fn _pop(vec: *mut Vec<SomeEnum>) -> __swift_bridge__Option_SomeEnum {
let vec = unsafe { &mut *vec };
let val = vec.pop();
__swift_bridge__Option_SomeEnum::from_rust_repr(val)
}

#[doc(hidden)]
#[export_name = "__swift_bridge__$Vec_SomeEnum$as_ptr"]
pub extern "C" fn _as_ptr(vec: *const Vec<SomeEnum>) -> *const SomeEnum {
unsafe { & *vec }.as_ptr()
}
};
})
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
extension SomeEnum: Vectorizable {
public static func vecOfSelfNew() -> UnsafeMutableRawPointer {
__swift_bridge__$Vec_SomeEnum$new()
}
public static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) {
__swift_bridge__$Vec_SomeEnum$drop(vecPtr)
}
public static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) {
__swift_bridge__$Vec_SomeEnum$push(vecPtr, value.intoFfiRepr())
}
public static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional<Self> {
let maybeEnum = __swift_bridge__$Vec_SomeEnum$pop(vecPtr)
return maybeEnum.intoSwiftRepr()
}
public static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {
let maybeEnum = __swift_bridge__$Vec_SomeEnum$get(vecPtr, index)
return maybeEnum.intoSwiftRepr()
}
public static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {
let maybeEnum = __swift_bridge__$Vec_SomeEnum$get_mut(vecPtr, index)
return maybeEnum.intoSwiftRepr()
}
public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt {
__swift_bridge__$Vec_SomeEnum$len(vecPtr)
}
}
"#,
)
}

fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsAfterTrim(
r#"
void* __swift_bridge__$Vec_SomeEnum$new(void);
void __swift_bridge__$Vec_SomeEnum$drop(void* vec_ptr);
void __swift_bridge__$Vec_SomeEnum$push(void* vec_ptr, __swift_bridge__$SomeEnum item);
__swift_bridge__$Option$SomeEnum __swift_bridge__$Vec_SomeEnum$pop(void* vec_ptr);
__swift_bridge__$Option$SomeEnum __swift_bridge__$Vec_SomeEnum$get(void* vec_ptr, uintptr_t index);
__swift_bridge__$Option$SomeEnum __swift_bridge__$Vec_SomeEnum$get_mut(void* vec_ptr, uintptr_t index);
uintptr_t __swift_bridge__$Vec_SomeEnum$len(void* vec_ptr);
void* __swift_bridge__$Vec_SomeEnum$as_ptr(void* vec_ptr);
"#,
)
}

#[test]
fn transparent_enum_vec_support() {
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();
}
}

/// Test code generation for Rust function that returns a Vec<T> where T is a transparent enum.
mod extern_rust_fn_return_vec_of_transparent_enum {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeEnum {
A
}

extern "Rust" {
fn some_function() -> Vec<SomeEnum>;
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
pub extern "C" fn __swift_bridge__some_function() -> *mut Vec<SomeEnum> {
Box::into_raw(Box::new(super::some_function()))
}
})
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function() -> RustVec<SomeEnum> {
RustVec(ptr: __swift_bridge__$some_function())
}
"#,
)
}

fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsAfterTrim(
r#"
void* __swift_bridge__$some_function(void);
"#,
)
}

#[test]
fn extern_rust_fn_return_vec_of_transparent_enum() {
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();
}
}

/// Test code generation for Rust function that has an argument
/// Vec<T> where T is a transparent enum.
mod extern_rust_fn_arg_vec_of_transparent_enum {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeEnum {
A
}
extern "Rust" {
type MyRustType;
fn some_function(arg: Vec<SomeEnum>);
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
pub extern "C" fn __swift_bridge__some_function(
arg: *mut Vec<SomeEnum>
) {
super::some_function(unsafe { * Box::from_raw(arg) })
}
})
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function(_ arg: RustVec<SomeEnum>) {
__swift_bridge__$some_function({ let val = arg; val.isOwned = false; return val.ptr }())
}
"#,
)
}

fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsAfterTrim(
r#"
void __swift_bridge__$some_function(void* arg);
"#,
)
}

#[test]
fn extern_rust_fn_arg_vec_of_transparent_enum() {
CodegenTest {
bridge_module: bridge_module_tokens().into(),
expected_rust_tokens: expected_rust_tokens(),
Expand Down
Loading

0 comments on commit c1fd717

Please sign in to comment.