Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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 @@ -136,7 +136,48 @@ class OpaqueRustStructTests: XCTestCase {
var table: [RustHashableType: String] = [:]
table[val1] = "hello"
table[val2] = "world"


//Should not be overwritten
if let element = table[val1] {
XCTAssertEqual(element, "hello")
}else {
XCTFail()
}
if let element = table[val2] {
XCTAssertEqual(element, "world")
}else {
XCTFail()
}
}
}

func testOpaqueRustCopyTypeImplHashable() throws {
XCTContext.runActivity(named: "Same hash value"){
_ in
let val1 = RustCopyHashableType(10)
let val2 = RustCopyHashableType(10)

var table: [RustCopyHashableType: String] = [:]
table[val1] = "hello"
table[val2] = "world"

//Should be overwritten.
if let element = table[val1] {
XCTAssertEqual(element, "world")
}else {
XCTFail()
}
}

XCTContext.runActivity(named: "Not same hash value"){
_ in
let val1 = RustCopyHashableType(10)
let val2 = RustCopyHashableType(100)

var table: [RustCopyHashableType: String] = [:]
table[val1] = "hello"
table[val2] = "world"

//Should not be overwritten
if let element = table[val1] {
XCTAssertEqual(element, "hello")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,18 +395,20 @@ void __swift_bridge__$SomeType$some_method_ref(struct __swift_bridge__$SomeType
}
}

/// Verify that we properly generate an Equatable extension for a Copy opaque Rust type.
/// Verify that we properly generate Equatable and Hashable extensions for a Copy opaque Rust type.
///
/// In May 2025 it was discovered that the Swift `Equatable` protocol was not being implemented for
/// Rust `Copy` types. This test case confirms tht we emit an `Equatable` protocol implementation.
/// Furthermore, in October 2025 it was discovered that the Swift `Hashable` protocol was not being implemented for
/// Rust `Copy` types. This test case confirms tht we emit an `Hashable` protocol implementation.
mod extern_rust_copy_type_equatable {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
#[swift_bridge(Copy(16), Equatable)]
#[swift_bridge(Copy(16), Equatable, Hashable)]
type SomeType;
}
}
Expand Down Expand Up @@ -434,6 +436,17 @@ extension SomeType: Equatable {
})
}
}

extension SomeType: Hashable {
public func hash(into hasher: inout Hasher){
var this = self
return withUnsafePointer(to: &this.bytes, {(ptr: UnsafePointer<__swift_bridge__$SomeType>) in
hasher.combine(__swift_bridge__$SomeType$_hash(
UnsafeMutablePointer(mutating: ptr)
))
})
}
}
"#,
)
}
Expand All @@ -446,6 +459,9 @@ extension SomeType: Equatable {
"#,
r#"
bool __swift_bridge__$SomeType$_partial_eq(__swift_bridge__$SomeType* lhs, __swift_bridge__$SomeType* rhs);
"#,
r#"
uint64_t __swift_bridge__$SomeType$_hash(__swift_bridge__$SomeType* self);
"#,
])
}
Expand Down
19 changes: 13 additions & 6 deletions crates/swift-bridge-ir/src/codegen/generate_c_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,6 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
if ty.attributes.declare_generic {
continue;
}
if ty.attributes.hashable {
let ty_name = ty.ty_name_ident();
let hash_ty =
format!("uint64_t __swift_bridge__${}$_hash(void* self);", ty_name);
header += &hash_ty;
}
let ty_name = ty.to_string();

if let Some(copy) = ty.attributes.copy {
Expand Down Expand Up @@ -333,6 +327,19 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
header += &equal_ty;
header += "\n";
}
if ty.attributes.hashable {
let ty_name = ty.ty_name_ident();
let hash_ty = format!(
"uint64_t __swift_bridge__${ty_name}$_hash({c_ffi_type}* self);",
ty_name = ty_name,
c_ffi_type = if ty.attributes.copy.is_some() {
&ty.ffi_repr_name_string()
} else {
"void"
},
);
header += &hash_ty;
}

// TODO: Support Vec<OpaqueCopyType>. Add codegen tests and then
// make them pass.
Expand Down
2 changes: 1 addition & 1 deletion crates/swift-bridge-ir/src/codegen/generate_swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ impl SwiftBridgeModule {

if ty.attributes.sendable {
let ty_name = ty.ty_name_string();
swift += &format!("extension {ty_name}: @unchecked Sendable {{}}")
swift += &format!("extension {ty_name}: @unchecked Sendable {{}}\n")
}
}
HostLang::Swift => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,31 @@ extension {type_name}: Equatable {{
String::new()
};

let ext_hashable = if ty.attributes.hashable {
format!(
r#"
extension {type_name}: Hashable {{
public func hash(into hasher: inout Hasher){{
var this = self
return withUnsafePointer(to: &this.bytes, {{(ptr: UnsafePointer<{ffi_repr_name}>) in
hasher.combine(__swift_bridge__${type_name}$_hash(
UnsafeMutablePointer(mutating: ptr)
))
}})
}}
}}
"#,
type_name = type_name,
ffi_repr_name = ty.ffi_repr_name_string()
)
} else {
String::new()
};

format!(
r#"{declare_struct}
{ffi_repr_conversion}
{ext_equatable}"#,
declare_struct = declare_struct,
ffi_repr_conversion = ffi_repr_conversion,
ext_equatable = ext_equatable,
{ext_equatable}{ext_hashable}"#,
)
}

Expand Down
10 changes: 5 additions & 5 deletions crates/swift-integration-tests/build.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::path::PathBuf;
use std::{ffi::OsStr, path::PathBuf};

fn main() {
let out_dir = "../../SwiftRustIntegrationTestRunner/Generated";
let out_dir = PathBuf::from(out_dir);

let mut bridges = vec![];
read_files_recursive(PathBuf::from("src"), &mut bridges);
read_files_recursive(PathBuf::from("src"), &mut bridges, OsStr::new("rs"));

for path in &bridges {
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
Expand All @@ -15,12 +15,12 @@ fn main() {
.write_all_concatenated(out_dir, env!("CARGO_PKG_NAME"));
}

fn read_files_recursive(dir: PathBuf, files: &mut Vec<PathBuf>) {
fn read_files_recursive(dir: PathBuf, files: &mut Vec<PathBuf>, extension: &OsStr) {
for entry in std::fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
read_files_recursive(path, files);
} else {
read_files_recursive(path, files, extension);
} else if path.extension() == Some(extension) {
files.push(path)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ mod ffi {

#[swift_bridge(init)]
fn new(num: isize) -> RustHashableType;

#[swift_bridge(Copy(4), Hashable, Equatable)]
type RustCopyHashableType;

#[swift_bridge(init)]
fn new(num: i32) -> RustCopyHashableType;
}
}

Expand All @@ -17,3 +23,12 @@ impl RustHashableType {
RustHashableType(num)
}
}

#[derive(Clone, Copy, Hash, PartialEq)]
pub struct RustCopyHashableType(i32);

impl RustCopyHashableType {
fn new(num: i32) -> Self {
Self(num)
}
}