Skip to content

Commit

Permalink
Add attribute for Equatable protocol (#139)
Browse files Browse the repository at this point in the history
This commit introduces the #[swift_bridge(Equatable)] attribute, which is used to generate a type that conforms to ```Equatable``` protocol.

Somthing like: 
```rust
// Rust 
mod ffi {
    extern "Rust" {
        #[swift_bridge(Equatable)]
        type SomeType;
    }
}
```

```Swift
// Generated Swift
class SomeType {
    // ...
}

// SomeType inherits SomeTypeRef. So, SomeType also conforms to Equatable protocol.
extension SomeTypeRef: Equatable {
    public static func == (lhs: SomeTypeRef, rhs: SomeTypeRef) -> Bool {
        __swift_bridge__$SomeTypeType$_partial_eq(rhs.ptr, lhs.ptr)
    }
}
```
  • Loading branch information
NiwakaDev authored Jan 18, 2023
1 parent cd37985 commit 3c55e2f
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,26 @@ class OpaqueRustStructTests: XCTestCase {

XCTAssert(val.eq(val2))
}

func testOpaqueRustTypeImplEquatable() throws {
XCTContext.runActivity(named: "Should be equal"){
_ in
let val1 = RustEquatableType()
let val2 = RustEquatableType()

XCTAssertEqual(val1, val2)
}

XCTContext.runActivity(named: "Should not be equal"){
_ in
let val1 = RustEquatableType()
let val2 = RustEquatableType()

val1.set_value(11)
val2.set_value(22)

XCTAssertNotEqual(val1, val2)
}
}
}

33 changes: 33 additions & 0 deletions book/src/bridge-module/opaque-types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,36 @@ mod ffi {
The `16` indicates that a `UserId` has 16 bytes.

`swift-bridge` will add a compile time assertion that confirms that the given size is correct.

#### #[swift_bridge(Equatable)]

You might want to make an opaque Rust type conform to ```Equatable```. If so, You don't need to implement manually ```Equatable``` for one. ```swift_bridge``` can do this automatically.

Here's an example:
```rust
//Rust side
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(Equatable)]
type RustEquatableType;

#[swift_bridge(init)]
fn new() -> RustEquatableType;
}
}

```

If you have the above code passed to ```swift_bridge```, you can use something like this:
```Swift
//Swift side
let val1 = RustEquatableType()
let val2 = RustEquatableType()

if val1 == val2 {
print("Equal")
} else {
print("Not equal")
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,69 @@ void __swift_bridge__$SomeType$_free(void* self);
}
}

/// Test code generation for an extern "Rust" type that implements Equatable.
mod extern_rust_equatable_type {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
#[swift_bridge(Equatable)]
type EquatableType;
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$EquatableType$_partial_eq"]
pub extern "C" fn __swift_bridge__EquatableType__partial_eq (
lhs: *const super::EquatableType,
rhs: *const super::EquatableType
) -> bool {
unsafe { &*lhs == &*rhs }
}
})
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
extension EquatableTypeRef: Equatable {
public static func == (lhs: EquatableTypeRef, rhs: EquatableTypeRef) -> Bool {
__swift_bridge__$EquatableType$_partial_eq(rhs.ptr, lhs.ptr)
}
}
"#,
)
}

fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
bool __swift_bridge__$EquatableType$_partial_eq(void* lhs, void* rhs);
"#,
r#"
#include <stdint.h>
#include <stdbool.h>
"#,
])
}

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

/// Test code generation for an extern "Rust" type that implements Copy.
mod extern_rust_copy_type {
use super::*;
Expand Down
12 changes: 11 additions & 1 deletion crates/swift-bridge-ir/src/codegen/generate_c_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,17 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
if ty.attributes.declare_generic {
continue;
}

if ty.attributes.equatable {
let ty_name = ty.ty_name_ident();
let equal_ty = format!(
"bool __swift_bridge__${}$_partial_eq(void* lhs, void* rhs);",
ty_name
);
bookkeeping.includes.insert("stdint.h");
bookkeeping.includes.insert("stdbool.h");
header += &equal_ty;
header += "\n";
}
let ty_name = ty.to_string();

if let Some(copy) = ty.attributes.copy {
Expand Down
18 changes: 18 additions & 0 deletions crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,24 @@ impl ToTokens for SwiftBridgeModule {

match ty.host_lang {
HostLang::Rust => {
if ty.attributes.equatable {
let export_name =
format!("__swift_bridge__${}$_partial_eq", ty_name);
let function_name = syn::Ident::new(
&format!("__swift_bridge__{}__partial_eq", ty_name),
ty.ty.span(),
);
let tokens = quote! {
#[export_name = #export_name]
pub extern "C" fn #function_name (
lhs: *const super::#ty_name,
rhs: *const super::#ty_name
) -> bool {
unsafe { &*lhs == &*rhs }
}
};
extern_rust_fn_tokens.push(tokens);
}
if let Some(copy) = ty.attributes.copy {
let size = copy.size_bytes;

Expand Down
20 changes: 17 additions & 3 deletions crates/swift-bridge-ir/src/codegen/generate_swift/swift_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,32 @@ where {swift_generic_bounds} {{
free_func_name = ty.free_rust_opaque_type_ffi_name()
);
}

let equatable_method: String = {
if ty.attributes.equatable {
let ty_name = ty.ty_name_ident();
format!(
r#"
extension {ty_name}Ref: Equatable {{
public static func == (lhs: {ty_name}Ref, rhs: {ty_name}Ref) -> Bool {{
__swift_bridge__${ty_name}$_partial_eq(rhs.ptr, lhs.ptr)
}}
}}"#,
)
} else {
"".to_string()
}
};
let class = format!(
r#"
{class_decl}{initializers}{owned_instance_methods}{class_ref_decl}{ref_mut_instance_methods}{class_ref_mut_decl}{ref_instance_methods}{generic_freer}"#,
{class_decl}{initializers}{owned_instance_methods}{class_ref_decl}{ref_mut_instance_methods}{class_ref_mut_decl}{ref_instance_methods}{generic_freer}{equatable_method}"#,
class_decl = class_decl,
class_ref_decl = class_ref_mut_decl,
class_ref_mut_decl = class_ref_decl,
initializers = initializers,
owned_instance_methods = owned_instance_methods,
ref_mut_instance_methods = ref_mut_instance_methods,
ref_instance_methods = ref_instance_methods,
generic_freer = generic_freer
equatable_method = equatable_method
);

return class;
Expand Down
26 changes: 26 additions & 0 deletions crates/swift-bridge-ir/src/parse/parse_extern_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,32 @@ mod tests {
);
}

/// 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub(crate) struct OpaqueTypeSwiftBridgeAttributes {
/// `#[swift_bridge(declare_generic)]`
/// Used to declare a generic type.
pub declare_generic: bool,
/// `#[swift_bridge(Equatable)]`
/// Used to determine if Equatable need to be implemented.
pub equatable: bool,
}

impl OpaqueTypeAllAttributes {
Expand Down Expand Up @@ -69,6 +72,7 @@ impl OpaqueTypeSwiftBridgeAttributes {
OpaqueTypeAttr::AlreadyDeclared => self.already_declared = true,
OpaqueTypeAttr::Copy { size } => self.copy = Some(OpaqueCopy { size_bytes: size }),
OpaqueTypeAttr::DeclareGeneric => self.declare_generic = true,
OpaqueTypeAttr::Equatable => self.equatable = true,
}
}
}
Expand All @@ -77,6 +81,7 @@ pub(crate) enum OpaqueTypeAttr {
AlreadyDeclared,
Copy { size: usize },
DeclareGeneric,
Equatable,
}

impl Parse for OpaqueTypeSwiftBridgeAttributes {
Expand Down Expand Up @@ -112,6 +117,7 @@ impl Parse for OpaqueTypeAttr {
}
}
"declare_generic" => OpaqueTypeAttr::DeclareGeneric,
"Equatable" => OpaqueTypeAttr::Equatable,
_ => {
let attrib = key.to_string();
Err(syn::Error::new_spanned(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod already_declared;
mod copy;
mod equatable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(Equatable)]
type RustEquatableType;

#[swift_bridge(init)]
fn new() -> RustEquatableType;

fn set_value(&mut self, value: i32);
}
}

#[derive(PartialEq)]
pub struct RustEquatableType(i32);

impl RustEquatableType {
fn new() -> Self {
RustEquatableType(0)
}

fn set_value(&mut self, value: i32) {
self.0 = value;
}
}

0 comments on commit 3c55e2f

Please sign in to comment.