Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add attribute for Equatable protocol #139

Merged
merged 3 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
}

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;
}
}