From 5155eed4bba461ba40b5fa52d27b00f3acad918a Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Sat, 11 Mar 2023 00:09:16 +0000 Subject: [PATCH 01/14] Adding Vec support for transparent structs --- .../VecTests.swift | 15 + .../transparent_struct_codegen_tests.rs | 10 +- .../codegen_tests/vec_codegen_tests.rs | 264 ++++++++++++++++++ .../src/codegen/generate_c_header.rs | 23 +- .../generate_rust_tokens/shared_struct.rs | 10 + .../src/codegen/generate_rust_tokens/vec.rs | 1 + .../vec/vec_of_transparent_struct.rs | 180 ++++++++++++ .../codegen/generate_swift/shared_struct.rs | 42 ++- crates/swift-integration-tests/src/vec.rs | 18 ++ 9 files changed, 554 insertions(+), 9 deletions(-) create mode 100644 crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift index d06f6446..c331b640 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift @@ -92,6 +92,21 @@ class VecTests: XCTestCase { XCTAssertEqual(reflected.get(index: 0)!, TransparentEnumInsideVecT.VariantB) XCTAssertEqual(reflected.pop()!, TransparentEnumInsideVecT.VariantB) } + + /// Verify that a Vec of transparent struct can be used as an argument and return + /// type for extern "Rust" functions. + func testReflectVecOfTransparentStruct() throws { + let vec: RustVec = RustVec() + vec.push(value: TransparentStructInsideVecT(string: "string".intoRustString(), integer: 10)) + + let reflected = rust_reflect_vec_transparent_struct(vec) + XCTAssertEqual(reflected.len(), 1) + XCTAssertEqual(reflected.get(index: 0)!.string.toString(), "string") + XCTAssertEqual(reflected.get(index: 0)!.integer, 10) + let popped = try XCTUnwrap(reflected.pop()) + XCTAssertEqual(popped.string.toString(), "string") + XCTAssertEqual(popped.integer, 10) + } /// 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 diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_struct_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_struct_codegen_tests.rs index d34dbc87..81f12143 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_struct_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/transparent_struct_codegen_tests.rs @@ -73,7 +73,7 @@ extension __swift_bridge__$SomeStruct { } fn expected_c_header() -> ExpectedCHeader { - ExpectedCHeader::ExactAfterTrim( + ExpectedCHeader::ContainsAfterTrim( r#" #include #include @@ -680,15 +680,15 @@ func some_function(_ arg: Optional) -> Optional { } fn expected_c_header() -> ExpectedCHeader { - ExpectedCHeader::ExactAfterTrim( + ExpectedCHeader::ContainsManyAfterTrim(vec![ r#" #include #include typedef struct __swift_bridge__$SomeStruct { uint8_t field; } __swift_bridge__$SomeStruct; typedef struct __swift_bridge__$Option$SomeStruct { bool is_some; __swift_bridge__$SomeStruct val; } __swift_bridge__$Option$SomeStruct; -struct __swift_bridge__$Option$SomeStruct __swift_bridge__$some_function(struct __swift_bridge__$Option$SomeStruct arg); - "#, - ) +"#, + "struct __swift_bridge__$Option$SomeStruct __swift_bridge__$some_function(struct __swift_bridge__$Option$SomeStruct arg);", + ]) } #[test] diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs index 9725d376..831ee5f6 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs @@ -536,3 +536,267 @@ void __swift_bridge__$some_function(void* arg); .test(); } } + +/// Verify that we emit Rust, Swift and C header code that allows a transparent struct be used +/// within a Vec. +mod transparent_struct_vec_support { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + #[swift_bridge(swift_repr = "struct")] + struct SomeStruct { + string: String, + integer: i64, + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + const _: () = { + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$new"] + pub extern "C" fn _new() -> *mut Vec { + Box::into_raw(Box::new(Vec::new())) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$drop"] + pub extern "C" fn _drop(vec: *mut Vec) { + let vec = unsafe { Box::from_raw(vec) }; + drop(vec) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$len"] + pub extern "C" fn _len(vec: *const Vec) -> usize { + unsafe { &*vec }.len() + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get"] + pub extern "C" fn _get(vec: *const Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &*vec }; + let val = vec.get(index).map(|v| v.clone()); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get_mut"] + pub extern "C" fn _get_mut(vec: *mut Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.get_mut(index).map(|v| v.clone()); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$push"] + pub extern "C" fn _push(vec: *mut Vec, val: __swift_bridge__SomeStruct) { + unsafe { &mut *vec }.push(val.into_rust_repr()) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$pop"] + pub extern "C" fn _pop(vec: *mut Vec) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.pop(); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$as_ptr"] + pub extern "C" fn _as_ptr(vec: *const Vec) -> *const SomeStruct { + unsafe { & *vec }.as_ptr() + } + }; + }) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsAfterTrim( + r#" +extension SomeStruct: Vectorizable { + public static func vecOfSelfNew() -> UnsafeMutableRawPointer { + __swift_bridge__$Vec_SomeStruct$new() + } + + public static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) { + __swift_bridge__$Vec_SomeStruct$drop(vecPtr) + } + + public static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) { + __swift_bridge__$Vec_SomeStruct$push(vecPtr, value.intoFfiRepr()) + } + + public static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$pop(vecPtr) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$get(vecPtr, index) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$get_mut(vecPtr, index) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt { + __swift_bridge__$Vec_SomeStruct$len(vecPtr) + } +} +"#, + ) + } + + fn expected_c_header() -> ExpectedCHeader { + ExpectedCHeader::ContainsAfterTrim( + r#" +void* __swift_bridge__$Vec_SomeStruct$new(void); +void __swift_bridge__$Vec_SomeStruct$drop(void* vec_ptr); +void __swift_bridge__$Vec_SomeStruct$push(void* vec_ptr, __swift_bridge__$SomeStruct item); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$pop(void* vec_ptr); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$get(void* vec_ptr, uintptr_t index); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$get_mut(void* vec_ptr, uintptr_t index); +uintptr_t __swift_bridge__$Vec_SomeStruct$len(void* vec_ptr); +void* __swift_bridge__$Vec_SomeStruct$as_ptr(void* vec_ptr); +"#, + ) + } + + #[test] + fn transparent_struct_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 where T is a transparent struct. +mod extern_rust_fn_return_vec_of_transparent_struct { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + #[swift_bridge(swift_repr = "struct")] + struct SomeStruct { + string: String, + integer: i64, + } + + extern "Rust" { + fn some_function() -> Vec; + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + pub extern "C" fn __swift_bridge__some_function() -> *mut Vec { + Box::into_raw(Box::new(super::some_function())) + } + }) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsAfterTrim( + r#" +func some_function() -> RustVec { + 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_struct() { + 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 where T is a transparent struct. +mod extern_rust_fn_arg_vec_of_transparent_struct { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + #[swift_bridge(swift_repr = "struct")] + struct SomeStruct { + string: String, + integer: i64, + } + + extern "Rust" { + type MyRustType; + fn some_function(arg: Vec); + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + pub extern "C" fn __swift_bridge__some_function( + arg: *mut Vec + ) { + super::some_function(unsafe { * Box::from_raw(arg) }) + } + }) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsAfterTrim( + r#" +func some_function(_ arg: RustVec) { + __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_struct() { + 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(); + } +} diff --git a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs index e994ad97..7a12437d 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs @@ -112,14 +112,17 @@ impl SwiftBridgeModule { "".to_string() }; + let vec_support = vec_transparent_struct_c_support(&name); + let ty_decl = format!( r#"typedef struct {prefix}${name} {{{maybe_fields}}} {prefix}${name}; -typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};"#, +typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};{vec_support}"#, prefix = SWIFT_BRIDGE_PREFIX, ffi_name = ffi_name, option_ffi_name = option_ffi_name, name = name, - maybe_fields = maybe_fields + maybe_fields = maybe_fields, + vec_support = vec_support ); header += &ty_decl; @@ -391,6 +394,22 @@ void* __swift_bridge__$Vec_{enum_name}$as_ptr(void* vec_ptr); ) } +fn vec_transparent_struct_c_support(struct_name: &str) -> String { + format!( + r#" +void* __swift_bridge__$Vec_{struct_name}$new(void); +void __swift_bridge__$Vec_{struct_name}$drop(void* vec_ptr); +void __swift_bridge__$Vec_{struct_name}$push(void* vec_ptr, __swift_bridge__${struct_name} item); +__swift_bridge__$Option${struct_name} __swift_bridge__$Vec_{struct_name}$pop(void* vec_ptr); +__swift_bridge__$Option${struct_name} __swift_bridge__$Vec_{struct_name}$get(void* vec_ptr, uintptr_t index); +__swift_bridge__$Option${struct_name} __swift_bridge__$Vec_{struct_name}$get_mut(void* vec_ptr, uintptr_t index); +uintptr_t __swift_bridge__$Vec_{struct_name}$len(void* vec_ptr); +void* __swift_bridge__$Vec_{struct_name}$as_ptr(void* vec_ptr); +"#, + struct_name = struct_name + ) +} + fn declare_func( func: &ParsedExternFn, bookkeeping: &mut Bookkeeping, diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs index a8a37399..ac8890a6 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs @@ -3,6 +3,7 @@ use crate::bridged_type::{BridgedType, SharedStruct}; use crate::{SwiftBridgeModule, SWIFT_BRIDGE_PREFIX}; +use crate::codegen::generate_rust_tokens::vec::vec_of_transparent_struct::generate_vec_of_transparent_struct_functions; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::Ident; @@ -90,7 +91,14 @@ impl SwiftBridgeModule { } }; + // TODO: + // Parse any derives that the user has specified and combine those with our auto derives. + let automatic_derives = vec![quote! {Clone}]; + + let vec_support = generate_vec_of_transparent_struct_functions(&shared_struct); + let definition = quote! { + #[derive(#(#automatic_derives),*)] pub struct #struct_name #struct_fields #struct_ffi_repr @@ -149,6 +157,8 @@ impl SwiftBridgeModule { } } } + + #vec_support }; Some(definition) diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec.rs index 1135e38e..4ae7b039 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec.rs @@ -1,2 +1,3 @@ pub(super) mod vec_of_opaque_rust_type; pub(super) mod vec_of_transparent_enum; +pub(super) mod vec_of_transparent_struct; diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs new file mode 100644 index 00000000..f61edf50 --- /dev/null +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs @@ -0,0 +1,180 @@ +use crate::bridged_type::{SharedStruct}; +use proc_macro2::{TokenStream}; +use quote::quote; + + +/// Generate the functions that Swift calls uses inside of the corresponding class for a +/// transparent struct's Vectorizable implementation. +/// +/// So inside of `extension SomeTransparentStruct: Vectorizable {}` on the Swift side. +pub(in super::super) fn generate_vec_of_transparent_struct_functions( + shared_struct: &SharedStruct, +) -> TokenStream { + let struct_name = &shared_struct.name; + + // examples: + // "__swift_bridge__$Vec_SomeTransparentStruct$new" + // "__swift_bridge__$Vec_SomeTransparentStruct$drop" + let make_export_name = |fn_name| { + format!( + "__swift_bridge__$Vec_{}${}", + shared_struct.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"); + let export_name_get = make_export_name("get"); + let export_name_get_mut = make_export_name("get_mut"); + let export_name_push = make_export_name("push"); + let export_name_pop = make_export_name("pop"); + let export_name_as_ptr = make_export_name("as_ptr"); + + let ffi_struct_repr = &shared_struct.ffi_name_tokens(); + let ffi_option_struct_repr = shared_struct.ffi_option_name_tokens(); + + quote! { + const _: () = { + #[doc(hidden)] + #[export_name = #export_name_new] + pub extern "C" fn _new() -> *mut Vec<#struct_name> { + Box::into_raw(Box::new(Vec::new())) + } + + #[doc(hidden)] + #[export_name = #export_name_drop] + pub extern "C" fn _drop(vec: *mut Vec<#struct_name>) { + let vec = unsafe { Box::from_raw(vec) }; + drop(vec) + } + + #[doc(hidden)] + #[export_name = #export_name_len] + pub extern "C" fn _len(vec: *const Vec<#struct_name>) -> usize { + unsafe { &*vec }.len() + } + + #[doc(hidden)] + #[export_name = #export_name_get] + pub extern "C" fn _get(vec: *const Vec<#struct_name>, index: usize) -> #ffi_option_struct_repr { + let vec = unsafe { &*vec }; + let val = vec.get(index).map(|v| v.clone()); + #ffi_option_struct_repr::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = #export_name_get_mut] + pub extern "C" fn _get_mut(vec: *mut Vec<#struct_name>, index: usize) -> #ffi_option_struct_repr { + let vec = unsafe { &mut *vec }; + let val = vec.get_mut(index).map(|v| v.clone()); + #ffi_option_struct_repr::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = #export_name_push] + pub extern "C" fn _push(vec: *mut Vec<#struct_name>, val: #ffi_struct_repr) { + unsafe { &mut *vec }.push( val.into_rust_repr() ) + } + + #[doc(hidden)] + #[export_name = #export_name_pop] + pub extern "C" fn _pop(vec: *mut Vec<#struct_name>) -> #ffi_option_struct_repr { + let vec = unsafe { &mut *vec }; + let val = vec.pop(); + #ffi_option_struct_repr::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = #export_name_as_ptr] + pub extern "C" fn _as_ptr(vec: *const Vec<#struct_name>) -> *const #struct_name { + unsafe { & *vec }.as_ptr() + } + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::assert_tokens_eq; + use proc_macro2::{Ident, Span}; + use crate::bridged_type::{StructSwiftRepr, StructFields}; + + /// 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 + /// side. + #[test] + fn generates_vectorizable_impl_for_opaque_rust_type() { + let expected = quote! { + const _: () = { + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$new"] + pub extern "C" fn _new() -> *mut Vec { + Box::into_raw(Box::new(Vec::new())) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$drop"] + pub extern "C" fn _drop(vec: *mut Vec) { + let vec = unsafe { Box::from_raw(vec) }; + drop(vec) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$len"] + pub extern "C" fn _len(vec: *const Vec) -> usize { + unsafe { &*vec }.len() + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get"] + pub extern "C" fn _get(vec: *const Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &*vec }; + let val = vec.get(index).map(|v| v.clone()); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get_mut"] + pub extern "C" fn _get_mut(vec: *mut Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.get_mut(index).map(|v| v.clone()); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$push"] + pub extern "C" fn _push(vec: *mut Vec, val: __swift_bridge__SomeStruct) { + unsafe { &mut *vec }.push(val.into_rust_repr()) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$pop"] + pub extern "C" fn _pop(vec: *mut Vec) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.pop(); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$as_ptr"] + pub extern "C" fn _as_ptr(vec: *const Vec) -> *const SomeStruct { + unsafe { & *vec }.as_ptr() + } + }; + }; + + let shared_struct = SharedStruct { + name: Ident::new("SomeStruct", Span::call_site()), + swift_repr: StructSwiftRepr::Structure, + fields: StructFields::Named(vec![]), + swift_name: None, + already_declared: false, + }; + assert_tokens_eq( + &generate_vec_of_transparent_struct_functions(&shared_struct), + &expected, + ); + } +} diff --git a/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs index 64864f0e..17e31923 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs @@ -47,6 +47,43 @@ impl SwiftBridgeModule { let convert_ffi_repr_to_swift = shared_struct.convert_ffi_expression_to_swift("self", &self.types); + let vectorizable_impl = format!( + r#" +extension {struct_name}: Vectorizable {{ + public static func vecOfSelfNew() -> UnsafeMutableRawPointer {{ + __swift_bridge__$Vec_{struct_name}$new() + }} + + public static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) {{ + __swift_bridge__$Vec_{struct_name}$drop(vecPtr) + }} + + public static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) {{ + __swift_bridge__$Vec_{struct_name}$push(vecPtr, value.intoFfiRepr()) + }} + + public static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional {{ + let maybeStruct = __swift_bridge__$Vec_{struct_name}$pop(vecPtr) + return maybeStruct.intoSwiftRepr() + }} + + public static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional {{ + let maybeStruct = __swift_bridge__$Vec_{struct_name}$get(vecPtr, index) + return maybeStruct.intoSwiftRepr() + }} + + public static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional {{ + let maybeStruct = __swift_bridge__$Vec_{struct_name}$get_mut(vecPtr, index) + return maybeStruct.intoSwiftRepr() + }} + + public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt {{ + __swift_bridge__$Vec_{struct_name}$len(vecPtr) + }} +}}"#, + struct_name = struct_name + ); + // No need to generate any code. Swift will automatically generate a // struct from our C header typedef that we generate for this struct. let swift_struct = format!( @@ -82,7 +119,7 @@ extension {option_ffi_name} {{ return {option_ffi_name}(is_some: false, val: {ffi_repr_name}()) }} }} -}}"#, +}}{vectorizable_impl}"#, struct_name = struct_name, initializer_params = initializer_params, initializer_body = initializer_body, @@ -90,7 +127,8 @@ extension {option_ffi_name} {{ ffi_repr_name = shared_struct.ffi_name_string(), option_ffi_name = option_ffi_name, convert_swift_to_ffi_repr = convert_swift_to_ffi_repr, - convert_ffi_repr_to_swift = convert_ffi_repr_to_swift + convert_ffi_repr_to_swift = convert_ffi_repr_to_swift, + vectorizable_impl = vectorizable_impl ); Some(swift_struct) diff --git a/crates/swift-integration-tests/src/vec.rs b/crates/swift-integration-tests/src/vec.rs index 45b8c04c..177a7020 100644 --- a/crates/swift-integration-tests/src/vec.rs +++ b/crates/swift-integration-tests/src/vec.rs @@ -5,6 +5,12 @@ mod ffi { VariantB, } + #[swift_bridge(swift_repr = "struct")] + struct TransparentStructInsideVecT { + string: String, + integer: i64, + } + extern "Rust" { type ARustTypeInsideVecT; @@ -25,6 +31,12 @@ mod ffi { arg: Vec, ) -> Vec; } + + extern "Rust" { + fn rust_reflect_vec_transparent_struct( + arg: Vec, + ) -> Vec; + } } pub struct ARustTypeInsideVecT { @@ -52,3 +64,9 @@ fn rust_reflect_vec_transparent_enum( ) -> Vec { arg } + +fn rust_reflect_vec_transparent_struct( + arg: Vec, +) -> Vec { + arg +} From 9fb33273bfa487460f64f7cfec9af46d08454b54 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 01:01:35 +0000 Subject: [PATCH 02/14] Adding derive() macro support to SharedStructs --- .../src/bridged_type/shared_struct.rs | 1 + .../generate_rust_tokens/shared_struct.rs | 8 ++ .../swift-bridge-ir/src/parse/parse_struct.rs | 90 +++++++++++++------ 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs b/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs index 5367d2d4..53988723 100644 --- a/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs +++ b/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs @@ -19,6 +19,7 @@ pub(crate) struct SharedStruct { pub fields: StructFields, pub swift_name: Option, pub already_declared: bool, + pub derives: Option>, } impl SharedStruct { diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs index a8a37399..c5bbc1b0 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs @@ -90,7 +90,15 @@ impl SwiftBridgeModule { } }; + let automatic_derives; + if let Some(derives) = shared_struct.derives.clone() { + automatic_derives = derives + } else { + automatic_derives = vec![]; + } + let definition = quote! { + #[derive(#(#automatic_derives),*)] pub struct #struct_name #struct_fields #struct_ffi_repr diff --git a/crates/swift-bridge-ir/src/parse/parse_struct.rs b/crates/swift-bridge-ir/src/parse/parse_struct.rs index 720b77cb..46244fc2 100644 --- a/crates/swift-bridge-ir/src/parse/parse_struct.rs +++ b/crates/swift-bridge-ir/src/parse/parse_struct.rs @@ -1,9 +1,10 @@ use crate::bridged_type::{SharedStruct, StructFields, StructSwiftRepr}; use crate::errors::{ParseError, ParseErrors}; use crate::parse::move_input_cursor_to_next_comma; -use proc_macro2::Ident; +use quote::ToTokens; +use proc_macro2::{Ident, TokenStream}; use syn::parse::{Parse, ParseStream}; -use syn::{ItemStruct, LitStr, Token}; +use syn::{ItemStruct, LitStr, Token, Meta}; pub(crate) struct SharedStructDeclarationParser<'a> { pub item_struct: ItemStruct, @@ -27,6 +28,7 @@ struct StructAttribs { swift_repr: Option<(StructSwiftRepr, LitStr)>, swift_name: Option, already_declared: bool, + derives: Option>, } struct ParsedAttribs(Vec); @@ -81,32 +83,47 @@ impl<'a> SharedStructDeclarationParser<'a> { let mut attribs = StructAttribs::default(); for attr in item_struct.attrs { - let sections: ParsedAttribs = attr.parse_args()?; - - for attr in sections.0 { - match attr { - StructAttr::SwiftRepr((repr, lit_str)) => { - attribs.swift_repr = Some((repr, lit_str)); - } - StructAttr::SwiftName(name) => { - attribs.swift_name = Some(name); + let attribute_name = attr.path.to_token_stream().to_string(); + + match attribute_name.as_str() { + "swift_bridge" => { + let sections: ParsedAttribs = attr.parse_args()?; + + for attr in sections.0 { + match attr { + StructAttr::SwiftRepr((repr, lit_str)) => { + attribs.swift_repr = Some((repr, lit_str)); + } + StructAttr::SwiftName(name) => { + attribs.swift_name = Some(name); + } + StructAttr::Error(err) => match err { + StructAttrParseError::InvalidSwiftRepr(val) => { + self.errors.push(ParseError::StructInvalidSwiftRepr { + swift_repr_attr_value: val.clone(), + }); + attribs.swift_repr = Some((StructSwiftRepr::Structure, val)); + } + StructAttrParseError::UnrecognizedAttribute(attribute) => { + self.errors + .push(ParseError::StructUnrecognizedAttribute { attribute }); + } + }, + StructAttr::AlreadyDeclared => { + attribs.already_declared = true; + } + }; } - StructAttr::Error(err) => match err { - StructAttrParseError::InvalidSwiftRepr(val) => { - self.errors.push(ParseError::StructInvalidSwiftRepr { - swift_repr_attr_value: val.clone(), - }); - attribs.swift_repr = Some((StructSwiftRepr::Structure, val)); + }, + "derive" => { + match attr.parse_meta()? { + Meta::List(meta_list) => { + attribs.derives = Some(meta_list.nested.iter().map(|val| val.to_token_stream()).collect()); } - StructAttrParseError::UnrecognizedAttribute(attribute) => { - self.errors - .push(ParseError::StructUnrecognizedAttribute { attribute }); - } - }, - StructAttr::AlreadyDeclared => { - attribs.already_declared = true; + _ => todo!("Push parse error that derive attribute is in incorrect format") } - }; + }, + _ => todo!("Push unsupported attribute error."), } } @@ -137,6 +154,7 @@ impl<'a> SharedStructDeclarationParser<'a> { fields: StructFields::from_syn_fields(item_struct.fields), swift_name: attribs.swift_name, already_declared: attribs.already_declared, + derives: attribs.derives, }; Ok(shared_struct) @@ -302,6 +320,25 @@ mod tests { assert_eq!(ty.swift_name.as_ref().unwrap().value(), "FfiFoo"); } + /// Verify that we parse the derive(...) + #[test] + fn parse_derive_attribute() { + let tokens = quote! { + #[swift_bridge::bridge] + mod ffi { + #[derive(Copy, Clone)] + struct Foo; + } + }; + + let module = parse_ok(tokens); + + let ty = module.types.types()[0].unwrap_shared_struct(); + + let derives: Vec = ty.derives.clone().unwrap().iter().map(|derive| derive.to_string()).collect(); + assert_eq!(derives, vec!["Copy", "Clone"]); + } + /// Verify that we properly parse multiple comma separated struct attributes. #[test] fn parses_multiple_struct_attributes() { @@ -309,6 +346,7 @@ mod tests { #[swift_bridge::bridge] mod ffi { #[swift_bridge(swift_name = "FfiFoo", swift_repr = "class")] + #[derive(Copy, Clone)] struct Foo { fied: u8 } @@ -320,6 +358,8 @@ mod tests { let ty = module.types.types()[0].unwrap_shared_struct(); assert_eq!(ty.swift_name.as_ref().unwrap().value(), "FfiFoo"); assert_eq!(ty.swift_repr, StructSwiftRepr::Class); + let derives: Vec = ty.derives.clone().unwrap().iter().map(|derive| derive.to_string()).collect(); + assert_eq!(derives, vec!["Copy", "Clone"]); } /// Verify that we can parse an `already_defined = "struct"` attribute. From 1a6af9271659a762a0e556e165efb16f6541d4c0 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 02:01:47 +0000 Subject: [PATCH 03/14] Checking for derives before generating vec support code --- .../VecTests.swift | 28 ++ .../codegen_tests/vec_codegen_tests.rs | 149 +++++++++- .../src/codegen/generate_c_header.rs | 7 +- .../src/codegen/generate_rust_tokens.rs | 2 + .../generate_rust_tokens/shared_struct.rs | 8 +- .../vec/vec_of_transparent_struct.rs | 268 ++++++++++++------ .../codegen/generate_swift/shared_struct.rs | 13 +- crates/swift-integration-tests/src/vec.rs | 19 ++ 8 files changed, 401 insertions(+), 93 deletions(-) diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift index c331b640..ed01bebc 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift @@ -107,6 +107,34 @@ class VecTests: XCTestCase { XCTAssertEqual(popped.string.toString(), "string") XCTAssertEqual(popped.integer, 10) } + + /// Verify that a Vec of transparent struct can be used as an argument and return + /// type for extern "Rust" functions. + func testReflectVecOfTransparentStructFromCopy() throws { + let vec: RustVec = RustVec() + vec.push(value: TransparentStructInsideVecTWithCopy(integer: 10)) + + let reflected = rust_reflect_vec_transparent_struct_with_copy(vec) + XCTAssertEqual(reflected.len(), 1) + XCTAssertEqual(reflected.get(index: 0)!.integer, 10) + let popped = try XCTUnwrap(reflected.pop()) + XCTAssertEqual(popped.integer, 10) + } + + /// Verify that a Vec of transparent struct can be used as an argument and return + /// type for extern "Rust" functions. + func testReflectVecOfTransparentStructFromClone() throws { + let vec: RustVec = RustVec() + vec.push(value: TransparentStructInsideVecT(string: "string".intoRustString(), integer: 10)) + + let reflected = rust_reflect_vec_transparent_struct(vec) + XCTAssertEqual(reflected.len(), 1) + XCTAssertEqual(reflected.get(index: 0)!.string.toString(), "string") + XCTAssertEqual(reflected.get(index: 0)!.integer, 10) + let popped = try XCTUnwrap(reflected.pop()) + XCTAssertEqual(popped.string.toString(), "string") + XCTAssertEqual(popped.integer, 10) + } /// 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 diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs index 831ee5f6..3a16f6d0 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs @@ -546,6 +546,7 @@ mod transparent_struct_vec_support { quote! { mod ffi { #[swift_bridge(swift_repr = "struct")] + #[derive(Clone)] struct SomeStruct { string: String, integer: i64, @@ -580,7 +581,7 @@ mod transparent_struct_vec_support { #[export_name = "__swift_bridge__$Vec_SomeStruct$get"] pub extern "C" fn _get(vec: *const Vec, index: usize) -> __swift_bridge__Option_SomeStruct { let vec = unsafe { &*vec }; - let val = vec.get(index).map(|v| v.clone()); + let val = vec.get(index).map(|v| v.clone() ); __swift_bridge__Option_SomeStruct::from_rust_repr(val) } @@ -588,7 +589,7 @@ mod transparent_struct_vec_support { #[export_name = "__swift_bridge__$Vec_SomeStruct$get_mut"] pub extern "C" fn _get_mut(vec: *mut Vec, index: usize) -> __swift_bridge__Option_SomeStruct { let vec = unsafe { &mut *vec }; - let val = vec.get_mut(index).map(|v| v.clone()); + let val = vec.get_mut(index).map(|v| v.clone() ); __swift_bridge__Option_SomeStruct::from_rust_repr(val) } @@ -681,6 +682,148 @@ void* __swift_bridge__$Vec_SomeStruct$as_ptr(void* vec_ptr); } } +mod transparent_struct_vec_support_with_copy { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + #[swift_bridge(swift_repr = "struct")] + #[derive(Copy, Clone)] + struct SomeStruct { + integer: i64, + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::Contains(quote! { + const _: () = { + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$new"] + pub extern "C" fn _new() -> *mut Vec { + Box::into_raw(Box::new(Vec::new())) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$drop"] + pub extern "C" fn _drop(vec: *mut Vec) { + let vec = unsafe { Box::from_raw(vec) }; + drop(vec) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$len"] + pub extern "C" fn _len(vec: *const Vec) -> usize { + unsafe { &*vec }.len() + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get"] + pub extern "C" fn _get(vec: *const Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &*vec }; + let val = vec.get(index).map(|v| *v ); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get_mut"] + pub extern "C" fn _get_mut(vec: *mut Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.get_mut(index).map(|v| *v ); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$push"] + pub extern "C" fn _push(vec: *mut Vec, val: __swift_bridge__SomeStruct) { + unsafe { &mut *vec }.push(val.into_rust_repr()) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$pop"] + pub extern "C" fn _pop(vec: *mut Vec) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.pop(); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$as_ptr"] + pub extern "C" fn _as_ptr(vec: *const Vec) -> *const SomeStruct { + unsafe { & *vec }.as_ptr() + } + }; + }) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::ContainsAfterTrim( + r#" +extension SomeStruct: Vectorizable { + public static func vecOfSelfNew() -> UnsafeMutableRawPointer { + __swift_bridge__$Vec_SomeStruct$new() + } + + public static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) { + __swift_bridge__$Vec_SomeStruct$drop(vecPtr) + } + + public static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) { + __swift_bridge__$Vec_SomeStruct$push(vecPtr, value.intoFfiRepr()) + } + + public static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$pop(vecPtr) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$get(vecPtr, index) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$get_mut(vecPtr, index) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt { + __swift_bridge__$Vec_SomeStruct$len(vecPtr) + } +} +"#, + ) + } + + fn expected_c_header() -> ExpectedCHeader { + ExpectedCHeader::ContainsAfterTrim( + r#" +void* __swift_bridge__$Vec_SomeStruct$new(void); +void __swift_bridge__$Vec_SomeStruct$drop(void* vec_ptr); +void __swift_bridge__$Vec_SomeStruct$push(void* vec_ptr, __swift_bridge__$SomeStruct item); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$pop(void* vec_ptr); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$get(void* vec_ptr, uintptr_t index); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$get_mut(void* vec_ptr, uintptr_t index); +uintptr_t __swift_bridge__$Vec_SomeStruct$len(void* vec_ptr); +void* __swift_bridge__$Vec_SomeStruct$as_ptr(void* vec_ptr); +"#, + ) + } + + #[test] + fn transparent_struct_vec_support_with_copy() { + 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 where T is a transparent struct. mod extern_rust_fn_return_vec_of_transparent_struct { use super::*; @@ -689,6 +832,7 @@ mod extern_rust_fn_return_vec_of_transparent_struct { quote! { mod ffi { #[swift_bridge(swift_repr = "struct")] + #[derive(Clone)] struct SomeStruct { string: String, integer: i64, @@ -748,6 +892,7 @@ mod extern_rust_fn_arg_vec_of_transparent_struct { quote! { mod ffi { #[swift_bridge(swift_repr = "struct")] + #[derive(Clone)] struct SomeStruct { string: String, integer: i64, diff --git a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs index 7a12437d..dbf96550 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs @@ -3,6 +3,7 @@ use crate::bridged_type::shared_struct::StructField; use crate::bridged_type::{BridgeableType, BridgedType, StdLibType, StructFields}; use crate::codegen::CodegenConfig; +use crate::codegen::generate_rust_tokens::can_generate_vec_of_transparent_struct_functions; use crate::parse::{SharedTypeDeclaration, TypeDeclaration, TypeDeclarations}; use crate::parsed_extern_fn::ParsedExternFn; use crate::{SwiftBridgeModule, SWIFT_BRIDGE_PREFIX}; @@ -112,7 +113,11 @@ impl SwiftBridgeModule { "".to_string() }; - let vec_support = vec_transparent_struct_c_support(&name); + let vec_support = if can_generate_vec_of_transparent_struct_functions(&ty_struct) { + vec_transparent_struct_c_support(&name) + } else { + format!("") + }; let ty_decl = format!( r#"typedef struct {prefix}${name} {{{maybe_fields}}} {prefix}${name}; diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs index 51d24ea3..4505d651 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens.rs @@ -11,6 +11,8 @@ use crate::bridge_module_attributes::CfgAttr; use crate::parse::{HostLang, SharedTypeDeclaration, TypeDeclaration}; use crate::SwiftBridgeModule; +pub(crate) use self::vec::vec_of_transparent_struct::can_generate_vec_of_transparent_struct_functions; + mod shared_enum; mod shared_struct; mod vec; diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs index 3d82841b..5523e77f 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs @@ -3,7 +3,7 @@ use crate::bridged_type::{BridgedType, SharedStruct}; use crate::{SwiftBridgeModule, SWIFT_BRIDGE_PREFIX}; -use crate::codegen::generate_rust_tokens::vec::vec_of_transparent_struct::generate_vec_of_transparent_struct_functions; +use crate::codegen::generate_rust_tokens::vec::vec_of_transparent_struct::{generate_vec_of_transparent_struct_functions, can_generate_vec_of_transparent_struct_functions}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::Ident; @@ -98,7 +98,11 @@ impl SwiftBridgeModule { automatic_derives = vec![]; } - let vec_support = generate_vec_of_transparent_struct_functions(&shared_struct); + let vec_support = if can_generate_vec_of_transparent_struct_functions(&shared_struct) { + generate_vec_of_transparent_struct_functions(&shared_struct) + } else { + quote! {} + }; let definition = quote! { #[derive(#(#automatic_derives),*)] diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs index f61edf50..ac299871 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs @@ -8,89 +8,110 @@ use quote::quote; /// /// So inside of `extension SomeTransparentStruct: Vectorizable {}` on the Swift side. pub(in super::super) fn generate_vec_of_transparent_struct_functions( - shared_struct: &SharedStruct, + shared_struct: &SharedStruct ) -> TokenStream { - let struct_name = &shared_struct.name; - - // examples: - // "__swift_bridge__$Vec_SomeTransparentStruct$new" - // "__swift_bridge__$Vec_SomeTransparentStruct$drop" - let make_export_name = |fn_name| { - format!( - "__swift_bridge__$Vec_{}${}", - shared_struct.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"); - let export_name_get = make_export_name("get"); - let export_name_get_mut = make_export_name("get_mut"); - let export_name_push = make_export_name("push"); - let export_name_pop = make_export_name("pop"); - let export_name_as_ptr = make_export_name("as_ptr"); - - let ffi_struct_repr = &shared_struct.ffi_name_tokens(); - let ffi_option_struct_repr = shared_struct.ffi_option_name_tokens(); - - quote! { - const _: () = { - #[doc(hidden)] - #[export_name = #export_name_new] - pub extern "C" fn _new() -> *mut Vec<#struct_name> { - Box::into_raw(Box::new(Vec::new())) - } - - #[doc(hidden)] - #[export_name = #export_name_drop] - pub extern "C" fn _drop(vec: *mut Vec<#struct_name>) { - let vec = unsafe { Box::from_raw(vec) }; - drop(vec) - } - - #[doc(hidden)] - #[export_name = #export_name_len] - pub extern "C" fn _len(vec: *const Vec<#struct_name>) -> usize { - unsafe { &*vec }.len() - } - - #[doc(hidden)] - #[export_name = #export_name_get] - pub extern "C" fn _get(vec: *const Vec<#struct_name>, index: usize) -> #ffi_option_struct_repr { - let vec = unsafe { &*vec }; - let val = vec.get(index).map(|v| v.clone()); - #ffi_option_struct_repr::from_rust_repr(val) - } - - #[doc(hidden)] - #[export_name = #export_name_get_mut] - pub extern "C" fn _get_mut(vec: *mut Vec<#struct_name>, index: usize) -> #ffi_option_struct_repr { - let vec = unsafe { &mut *vec }; - let val = vec.get_mut(index).map(|v| v.clone()); - #ffi_option_struct_repr::from_rust_repr(val) - } - - #[doc(hidden)] - #[export_name = #export_name_push] - pub extern "C" fn _push(vec: *mut Vec<#struct_name>, val: #ffi_struct_repr) { - unsafe { &mut *vec }.push( val.into_rust_repr() ) - } - - #[doc(hidden)] - #[export_name = #export_name_pop] - pub extern "C" fn _pop(vec: *mut Vec<#struct_name>) -> #ffi_option_struct_repr { - let vec = unsafe { &mut *vec }; - let val = vec.pop(); - #ffi_option_struct_repr::from_rust_repr(val) - } - - #[doc(hidden)] - #[export_name = #export_name_as_ptr] - pub extern "C" fn _as_ptr(vec: *const Vec<#struct_name>) -> *const #struct_name { - unsafe { & *vec }.as_ptr() - } + if can_generate_vec_of_transparent_struct_functions(&shared_struct) { + let struct_name = &shared_struct.name; + + // examples: + // "__swift_bridge__$Vec_SomeTransparentStruct$new" + // "__swift_bridge__$Vec_SomeTransparentStruct$drop" + let make_export_name = |fn_name| { + format!( + "__swift_bridge__$Vec_{}${}", + shared_struct.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"); + let export_name_get = make_export_name("get"); + let export_name_get_mut = make_export_name("get_mut"); + let export_name_push = make_export_name("push"); + let export_name_pop = make_export_name("pop"); + let export_name_as_ptr = make_export_name("as_ptr"); + + let ffi_struct_repr = &shared_struct.ffi_name_tokens(); + let ffi_option_struct_repr = shared_struct.ffi_option_name_tokens(); + // TODO: Check for trait implementation instead of derives + let derives: Vec = shared_struct.derives.as_ref().unwrap().iter().map(|derive| derive.to_string()).collect(); + let vec_map = if derives.contains(&"Copy".to_string()) { + quote! { *v } + } else { + quote! { v.clone() } + }; + + quote! { + const _: () = { + #[doc(hidden)] + #[export_name = #export_name_new] + pub extern "C" fn _new() -> *mut Vec<#struct_name> { + Box::into_raw(Box::new(Vec::new())) + } + + #[doc(hidden)] + #[export_name = #export_name_drop] + pub extern "C" fn _drop(vec: *mut Vec<#struct_name>) { + let vec = unsafe { Box::from_raw(vec) }; + drop(vec) + } + + #[doc(hidden)] + #[export_name = #export_name_len] + pub extern "C" fn _len(vec: *const Vec<#struct_name>) -> usize { + unsafe { &*vec }.len() + } + + #[doc(hidden)] + #[export_name = #export_name_get] + pub extern "C" fn _get(vec: *const Vec<#struct_name>, index: usize) -> #ffi_option_struct_repr { + let vec = unsafe { &*vec }; + let val = vec.get(index).map(|v|#vec_map); + #ffi_option_struct_repr::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = #export_name_get_mut] + pub extern "C" fn _get_mut(vec: *mut Vec<#struct_name>, index: usize) -> #ffi_option_struct_repr { + let vec = unsafe { &mut *vec }; + let val = vec.get_mut(index).map(|v|#vec_map); + #ffi_option_struct_repr::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = #export_name_push] + pub extern "C" fn _push(vec: *mut Vec<#struct_name>, val: #ffi_struct_repr) { + unsafe { &mut *vec }.push( val.into_rust_repr() ) + } + + #[doc(hidden)] + #[export_name = #export_name_pop] + pub extern "C" fn _pop(vec: *mut Vec<#struct_name>) -> #ffi_option_struct_repr { + let vec = unsafe { &mut *vec }; + let val = vec.pop(); + #ffi_option_struct_repr::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = #export_name_as_ptr] + pub extern "C" fn _as_ptr(vec: *const Vec<#struct_name>) -> *const #struct_name { + unsafe { & *vec }.as_ptr() + } + }; + } + } else { + quote! {} + } +} + +pub(crate) fn can_generate_vec_of_transparent_struct_functions(shared_struct: &SharedStruct) -> bool { + // TODO: Check for trait implementation instead of derives + if let Some(derives) = &shared_struct.derives { + let derives: Vec = derives.iter().map(|derive| derive.to_string()).collect(); + derives.contains(&"Copy".to_string()) || derives.contains(&"Clone".to_string()) + } else { + false } } @@ -105,7 +126,85 @@ mod tests { /// in order to power the `extension MyRustType: Vectorizable { }` implementation on the Swift /// side. #[test] - fn generates_vectorizable_impl_for_opaque_rust_type() { + fn generates_vectorizable_impl_for_shared_struct_with_copy() { + let expected = quote! { + const _: () = { + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$new"] + pub extern "C" fn _new() -> *mut Vec { + Box::into_raw(Box::new(Vec::new())) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$drop"] + pub extern "C" fn _drop(vec: *mut Vec) { + let vec = unsafe { Box::from_raw(vec) }; + drop(vec) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$len"] + pub extern "C" fn _len(vec: *const Vec) -> usize { + unsafe { &*vec }.len() + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get"] + pub extern "C" fn _get(vec: *const Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &*vec }; + let val = vec.get(index).map(|v| *v ); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get_mut"] + pub extern "C" fn _get_mut(vec: *mut Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.get_mut(index).map(|v| *v ); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$push"] + pub extern "C" fn _push(vec: *mut Vec, val: __swift_bridge__SomeStruct) { + unsafe { &mut *vec }.push(val.into_rust_repr()) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$pop"] + pub extern "C" fn _pop(vec: *mut Vec) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.pop(); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$as_ptr"] + pub extern "C" fn _as_ptr(vec: *const Vec) -> *const SomeStruct { + unsafe { & *vec }.as_ptr() + } + }; + }; + + let shared_struct = SharedStruct { + name: Ident::new("SomeStruct", Span::call_site()), + swift_repr: StructSwiftRepr::Structure, + fields: StructFields::Named(vec![]), + swift_name: None, + already_declared: false, + derives: Some(vec![quote! {Copy}, quote! {Clone}]), + }; + assert_tokens_eq( + &generate_vec_of_transparent_struct_functions(&shared_struct), + &expected, + ); + } + + /// 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 + /// side. + #[test] + fn generates_vectorizable_impl_for_shared_struct_with_clone() { let expected = quote! { const _: () = { #[doc(hidden)] @@ -131,7 +230,7 @@ mod tests { #[export_name = "__swift_bridge__$Vec_SomeStruct$get"] pub extern "C" fn _get(vec: *const Vec, index: usize) -> __swift_bridge__Option_SomeStruct { let vec = unsafe { &*vec }; - let val = vec.get(index).map(|v| v.clone()); + let val = vec.get(index).map(|v| v.clone() ); __swift_bridge__Option_SomeStruct::from_rust_repr(val) } @@ -139,7 +238,7 @@ mod tests { #[export_name = "__swift_bridge__$Vec_SomeStruct$get_mut"] pub extern "C" fn _get_mut(vec: *mut Vec, index: usize) -> __swift_bridge__Option_SomeStruct { let vec = unsafe { &mut *vec }; - let val = vec.get_mut(index).map(|v| v.clone()); + let val = vec.get_mut(index).map(|v| v.clone() ); __swift_bridge__Option_SomeStruct::from_rust_repr(val) } @@ -171,6 +270,7 @@ mod tests { fields: StructFields::Named(vec![]), swift_name: None, already_declared: false, + derives: Some(vec![quote! {Clone}]), }; assert_tokens_eq( &generate_vec_of_transparent_struct_functions(&shared_struct), diff --git a/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs index 17e31923..a90bfde4 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs @@ -1,5 +1,6 @@ use crate::bridged_type::shared_struct::StructField; use crate::bridged_type::{BridgedType, SharedStruct, StructFields, StructSwiftRepr, TypePosition}; +use crate::codegen::generate_rust_tokens::can_generate_vec_of_transparent_struct_functions; use crate::SwiftBridgeModule; impl SwiftBridgeModule { @@ -47,8 +48,9 @@ impl SwiftBridgeModule { let convert_ffi_repr_to_swift = shared_struct.convert_ffi_expression_to_swift("self", &self.types); - let vectorizable_impl = format!( - r#" + let vectorizable_impl = if can_generate_vec_of_transparent_struct_functions(&shared_struct) { + format!( + r#" extension {struct_name}: Vectorizable {{ public static func vecOfSelfNew() -> UnsafeMutableRawPointer {{ __swift_bridge__$Vec_{struct_name}$new() @@ -81,8 +83,11 @@ extension {struct_name}: Vectorizable {{ __swift_bridge__$Vec_{struct_name}$len(vecPtr) }} }}"#, - struct_name = struct_name - ); + struct_name = struct_name + ) + } else { + format!("") + }; // No need to generate any code. Swift will automatically generate a // struct from our C header typedef that we generate for this struct. diff --git a/crates/swift-integration-tests/src/vec.rs b/crates/swift-integration-tests/src/vec.rs index 177a7020..374bd8e6 100644 --- a/crates/swift-integration-tests/src/vec.rs +++ b/crates/swift-integration-tests/src/vec.rs @@ -6,11 +6,18 @@ mod ffi { } #[swift_bridge(swift_repr = "struct")] + #[derive(Clone)] struct TransparentStructInsideVecT { string: String, integer: i64, } + #[swift_bridge(swift_repr = "struct")] + #[derive(Copy, Clone)] + struct TransparentStructInsideVecTWithCopy { + integer: i64, + } + extern "Rust" { type ARustTypeInsideVecT; @@ -37,6 +44,12 @@ mod ffi { arg: Vec, ) -> Vec; } + + extern "Rust" { + fn rust_reflect_vec_transparent_struct_with_copy( + arg: Vec, + ) -> Vec; + } } pub struct ARustTypeInsideVecT { @@ -70,3 +83,9 @@ fn rust_reflect_vec_transparent_struct( ) -> Vec { arg } + +fn rust_reflect_vec_transparent_struct_with_copy( + arg: Vec, +) -> Vec { + arg +} From ead0af99013d5cafb5e2cb8d0da6ba7a191d8211 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 02:52:39 +0000 Subject: [PATCH 04/14] Code review --- .../src/bridged_type/shared_struct.rs | 8 ++- .../generate_rust_tokens/shared_struct.rs | 13 ++-- .../swift-bridge-ir/src/parse/parse_struct.rs | 59 ++++++++++++++++--- .../src/struct_attributes/derive.rs | 28 +++++++++ 4 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 crates/swift-integration-tests/src/struct_attributes/derive.rs diff --git a/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs b/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs index 53988723..bb9dde55 100644 --- a/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs +++ b/crates/swift-bridge-ir/src/bridged_type/shared_struct.rs @@ -19,7 +19,13 @@ pub(crate) struct SharedStruct { pub fields: StructFields, pub swift_name: Option, pub already_declared: bool, - pub derives: Option>, + pub derives: StructDerives, +} + +#[derive(Clone)] +pub(crate) struct StructDerives { + pub copy: bool, + pub clone: bool, } impl SharedStruct { diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs index c5bbc1b0..e9d9df31 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs @@ -90,15 +90,16 @@ impl SwiftBridgeModule { } }; - let automatic_derives; - if let Some(derives) = shared_struct.derives.clone() { - automatic_derives = derives - } else { - automatic_derives = vec![]; + let mut derives: Vec = vec![]; + if shared_struct.derives.copy { + derives.push(quote! {Copy}); + } + if shared_struct.derives.clone { + derives.push(quote! {Clone}); } let definition = quote! { - #[derive(#(#automatic_derives),*)] + #[derive(#(#derives),*)] pub struct #struct_name #struct_fields #struct_ffi_repr diff --git a/crates/swift-bridge-ir/src/parse/parse_struct.rs b/crates/swift-bridge-ir/src/parse/parse_struct.rs index 46244fc2..b5250a0d 100644 --- a/crates/swift-bridge-ir/src/parse/parse_struct.rs +++ b/crates/swift-bridge-ir/src/parse/parse_struct.rs @@ -1,8 +1,8 @@ -use crate::bridged_type::{SharedStruct, StructFields, StructSwiftRepr}; +use crate::bridged_type::{SharedStruct, shared_struct::StructDerives, StructFields, StructSwiftRepr}; use crate::errors::{ParseError, ParseErrors}; use crate::parse::move_input_cursor_to_next_comma; use quote::ToTokens; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::Ident; use syn::parse::{Parse, ParseStream}; use syn::{ItemStruct, LitStr, Token, Meta}; @@ -28,7 +28,16 @@ struct StructAttribs { swift_repr: Option<(StructSwiftRepr, LitStr)>, swift_name: Option, already_declared: bool, - derives: Option>, + derives: StructDerives, +} + +impl Default for StructDerives { + fn default() -> Self { + StructDerives { + copy: false, + clone: false, + } + } } struct ParsedAttribs(Vec); @@ -118,7 +127,13 @@ impl<'a> SharedStructDeclarationParser<'a> { "derive" => { match attr.parse_meta()? { Meta::List(meta_list) => { - attribs.derives = Some(meta_list.nested.iter().map(|val| val.to_token_stream()).collect()); + for derive in meta_list.nested { + match derive.to_token_stream().to_string().as_str() { + "Copy" => { attribs.derives.copy = true; }, + "Clone" => { attribs.derives.clone = true }, + _ => {} + } + } } _ => todo!("Push parse error that derive attribute is in incorrect format") } @@ -328,6 +343,9 @@ mod tests { mod ffi { #[derive(Copy, Clone)] struct Foo; + + #[derive(Clone)] + struct Bar; } }; @@ -335,13 +353,38 @@ mod tests { let ty = module.types.types()[0].unwrap_shared_struct(); - let derives: Vec = ty.derives.clone().unwrap().iter().map(|derive| derive.to_string()).collect(); - assert_eq!(derives, vec!["Copy", "Clone"]); + assert_eq!(ty.derives.copy, true); + assert_eq!(ty.derives.clone, true); + + let ty2 = module.types.types()[1].unwrap_shared_struct(); + + assert_eq!(ty2.derives.copy, false); + assert_eq!(ty2.derives.clone, true); } /// Verify that we properly parse multiple comma separated struct attributes. #[test] fn parses_multiple_struct_attributes() { + let tokens = quote! { + #[swift_bridge::bridge] + mod ffi { + #[swift_bridge(swift_name = "FfiFoo", swift_repr = "class")] + struct Foo { + fied: u8 + } + } + }; + + let module = parse_ok(tokens); + + let ty = module.types.types()[0].unwrap_shared_struct(); + assert_eq!(ty.swift_name.as_ref().unwrap().value(), "FfiFoo"); + assert_eq!(ty.swift_repr, StructSwiftRepr::Class); + } + + /// Verify that we properly parse multiple comma separated struct attributes and derive attributes. + #[test] + fn parses_multiple_struct_attributes_and_derive() { let tokens = quote! { #[swift_bridge::bridge] mod ffi { @@ -358,8 +401,8 @@ mod tests { let ty = module.types.types()[0].unwrap_shared_struct(); assert_eq!(ty.swift_name.as_ref().unwrap().value(), "FfiFoo"); assert_eq!(ty.swift_repr, StructSwiftRepr::Class); - let derives: Vec = ty.derives.clone().unwrap().iter().map(|derive| derive.to_string()).collect(); - assert_eq!(derives, vec!["Copy", "Clone"]); + assert_eq!(ty.derives.copy, true); + assert_eq!(ty.derives.clone, true); } /// Verify that we can parse an `already_defined = "struct"` attribute. diff --git a/crates/swift-integration-tests/src/struct_attributes/derive.rs b/crates/swift-integration-tests/src/struct_attributes/derive.rs new file mode 100644 index 00000000..68f99572 --- /dev/null +++ b/crates/swift-integration-tests/src/struct_attributes/derive.rs @@ -0,0 +1,28 @@ +#[swift_bridge::bridge] +mod ffi { + #[swift_bridge(swift_repr = "struct")] + #[derive(Copy, Clone)] + struct StructDeriveCopy1; + + #[swift_bridge(swift_repr = "struct")] + #[derive(Copy, Clone)] + struct StructDeriveCopy2 { + field: u8, + } + + #[swift_bridge(swift_repr = "struct")] + #[derive(Clone)] + struct StructDeriveClone1; + + #[swift_bridge(swift_repr = "struct")] + #[derive(Clone)] + struct StructDeriveClone2 { + field: u8, + } + + #[swift_bridge(swift_repr = "struct")] + #[derive(Clone)] + struct StructDeriveClone3 { + field: String, + } +} \ No newline at end of file From 91eaa862c3e8bba3059e25e3229a06de393f6765 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 03:12:48 +0000 Subject: [PATCH 05/14] Adjusting code to use StructDerives --- .../vec/vec_of_transparent_struct.rs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs index ac299871..db40eeba 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs @@ -34,9 +34,8 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( let ffi_struct_repr = &shared_struct.ffi_name_tokens(); let ffi_option_struct_repr = shared_struct.ffi_option_name_tokens(); - // TODO: Check for trait implementation instead of derives - let derives: Vec = shared_struct.derives.as_ref().unwrap().iter().map(|derive| derive.to_string()).collect(); - let vec_map = if derives.contains(&"Copy".to_string()) { + // TODO: Check for trait implementation as well + let vec_map = if shared_struct.derives.copy { quote! { *v } } else { quote! { v.clone() } @@ -106,13 +105,8 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( } pub(crate) fn can_generate_vec_of_transparent_struct_functions(shared_struct: &SharedStruct) -> bool { - // TODO: Check for trait implementation instead of derives - if let Some(derives) = &shared_struct.derives { - let derives: Vec = derives.iter().map(|derive| derive.to_string()).collect(); - derives.contains(&"Copy".to_string()) || derives.contains(&"Clone".to_string()) - } else { - false - } + // TODO: Check for trait implementation as well + shared_struct.derives.copy || shared_struct.derives.clone } #[cfg(test)] @@ -120,7 +114,7 @@ mod tests { use super::*; use crate::test_utils::assert_tokens_eq; use proc_macro2::{Ident, Span}; - use crate::bridged_type::{StructSwiftRepr, StructFields}; + use crate::bridged_type::{StructSwiftRepr, StructFields, shared_struct::StructDerives}; /// 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 @@ -192,7 +186,7 @@ mod tests { fields: StructFields::Named(vec![]), swift_name: None, already_declared: false, - derives: Some(vec![quote! {Copy}, quote! {Clone}]), + derives: StructDerives { copy: true, clone: true }, }; assert_tokens_eq( &generate_vec_of_transparent_struct_functions(&shared_struct), @@ -270,7 +264,7 @@ mod tests { fields: StructFields::Named(vec![]), swift_name: None, already_declared: false, - derives: Some(vec![quote! {Clone}]), + derives: StructDerives { copy: false, clone: true }, }; assert_tokens_eq( &generate_vec_of_transparent_struct_functions(&shared_struct), From c4c916f646d8da5915a0c13e5b7c33934a69c009 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 03:41:27 +0000 Subject: [PATCH 06/14] Adding integration test --- .../src/codegen/codegen_tests.rs | 1 + .../derive_struct_attribute_codegen_tests.rs | 93 +++++++++++++++++++ .../generate_rust_tokens/shared_struct.rs | 2 +- .../swift-bridge-ir/src/parse/parse_struct.rs | 35 +++---- 4 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute_codegen_tests.rs diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests.rs index cdb2f488..b57e61a4 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests.rs @@ -32,6 +32,7 @@ mod argument_label_codegen_tests; mod async_function_codegen_tests; mod boxed_fnonce_codegen_tests; mod conditional_compilation_codegen_tests; +mod derive_struct_attribute_codegen_tests; mod extern_rust_function_opaque_rust_type_argument_codegen_tests; mod extern_rust_function_opaque_rust_type_return_codegen_tests; mod extern_rust_method_swift_class_placement_codegen_tests; diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute_codegen_tests.rs new file mode 100644 index 00000000..8ed6c383 --- /dev/null +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute_codegen_tests.rs @@ -0,0 +1,93 @@ +use super::{CodegenTest, ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode}; +use proc_macro2::TokenStream; +use quote::quote; + +mod derive_copy_struct { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + #[swift_bridge::bridge] + mod ffi { + #[swift_bridge(swift_repr = "struct")] + #[derive(Copy, Clone)] + struct SomeStruct { + field: u8, + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::ContainsMany(vec![quote! { + #[derive(Copy, Clone)] + pub struct SomeStruct { + pub field: u8 + } + }]) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::SkipTest + } + + fn expected_c_header() -> ExpectedCHeader { + ExpectedCHeader::SkipTest + } + + #[test] + fn generates_struct() { + 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(); + } +} + +mod derive_clone_struct { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + #[swift_bridge::bridge] + mod ffi { + #[swift_bridge(swift_repr = "struct")] + #[derive(Clone)] + struct SomeStruct { + field: u8, + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::ContainsMany(vec![quote! { + #[derive(Clone)] + pub struct SomeStruct { + pub field: u8 + } + }]) + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::SkipTest + } + + fn expected_c_header() -> ExpectedCHeader { + ExpectedCHeader::SkipTest + } + + #[test] + fn generates_struct() { + 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(); + } +} diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs index e9d9df31..6adf67cc 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs @@ -99,7 +99,7 @@ impl SwiftBridgeModule { } let definition = quote! { - #[derive(#(#derives),*)] + #[derive(#(#derives),*)] pub struct #struct_name #struct_fields #struct_ffi_repr diff --git a/crates/swift-bridge-ir/src/parse/parse_struct.rs b/crates/swift-bridge-ir/src/parse/parse_struct.rs index b5250a0d..a14c462a 100644 --- a/crates/swift-bridge-ir/src/parse/parse_struct.rs +++ b/crates/swift-bridge-ir/src/parse/parse_struct.rs @@ -1,10 +1,12 @@ -use crate::bridged_type::{SharedStruct, shared_struct::StructDerives, StructFields, StructSwiftRepr}; +use crate::bridged_type::{ + shared_struct::StructDerives, SharedStruct, StructFields, StructSwiftRepr, +}; use crate::errors::{ParseError, ParseErrors}; use crate::parse::move_input_cursor_to_next_comma; -use quote::ToTokens; use proc_macro2::Ident; +use quote::ToTokens; use syn::parse::{Parse, ParseStream}; -use syn::{ItemStruct, LitStr, Token, Meta}; +use syn::{ItemStruct, LitStr, Meta, Token}; pub(crate) struct SharedStructDeclarationParser<'a> { pub item_struct: ItemStruct, @@ -114,8 +116,9 @@ impl<'a> SharedStructDeclarationParser<'a> { attribs.swift_repr = Some((StructSwiftRepr::Structure, val)); } StructAttrParseError::UnrecognizedAttribute(attribute) => { - self.errors - .push(ParseError::StructUnrecognizedAttribute { attribute }); + self.errors.push(ParseError::StructUnrecognizedAttribute { + attribute, + }); } }, StructAttr::AlreadyDeclared => { @@ -123,20 +126,18 @@ impl<'a> SharedStructDeclarationParser<'a> { } }; } - }, - "derive" => { - match attr.parse_meta()? { - Meta::List(meta_list) => { - for derive in meta_list.nested { - match derive.to_token_stream().to_string().as_str() { - "Copy" => { attribs.derives.copy = true; }, - "Clone" => { attribs.derives.clone = true }, - _ => {} - } + } + "derive" => match attr.parse_meta()? { + Meta::List(meta_list) => { + for derive in meta_list.nested { + match derive.to_token_stream().to_string().as_str() { + "Copy" => attribs.derives.copy = true, + "Clone" => attribs.derives.clone = true, + _ => {} } } - _ => todo!("Push parse error that derive attribute is in incorrect format") } + _ => todo!("Push parse error that derive attribute is in incorrect format"), }, _ => todo!("Push unsupported attribute error."), } @@ -352,7 +353,7 @@ mod tests { let module = parse_ok(tokens); let ty = module.types.types()[0].unwrap_shared_struct(); - + assert_eq!(ty.derives.copy, true); assert_eq!(ty.derives.clone, true); From 7eb667e84564867f5952b3a024b573af15dc8654 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 04:08:35 +0000 Subject: [PATCH 07/14] Adding extra check --- .../vec/vec_of_transparent_struct.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs index db40eeba..feeb3103 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs @@ -1,4 +1,4 @@ -use crate::bridged_type::{SharedStruct}; +use crate::bridged_type::{SharedStruct, StructSwiftRepr}; use proc_macro2::{TokenStream}; use quote::quote; @@ -105,8 +105,11 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( } pub(crate) fn can_generate_vec_of_transparent_struct_functions(shared_struct: &SharedStruct) -> bool { - // TODO: Check for trait implementation as well - shared_struct.derives.copy || shared_struct.derives.clone + match shared_struct.swift_repr { + StructSwiftRepr::Class => false, + // TODO: Check for trait implementation as well + StructSwiftRepr::Structure => shared_struct.derives.copy || shared_struct.derives.clone, + } } #[cfg(test)] From f7d2b7ea64e4737c1dffb20b7730326a27b97667 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 04:08:52 +0000 Subject: [PATCH 08/14] add missing mod --- crates/swift-integration-tests/src/struct_attributes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/swift-integration-tests/src/struct_attributes.rs b/crates/swift-integration-tests/src/struct_attributes.rs index 6fc2cf4d..61d040d2 100644 --- a/crates/swift-integration-tests/src/struct_attributes.rs +++ b/crates/swift-integration-tests/src/struct_attributes.rs @@ -1,2 +1,3 @@ mod already_declared; mod swift_name; +mod derive; From a789e6baa028b0eaba4342123dfd9f0490cb9436 Mon Sep 17 00:00:00 2001 From: Chinedu Francis Nwafili Date: Thu, 16 Mar 2023 00:15:54 -0400 Subject: [PATCH 09/14] Add doc comments to codegen tests --- .../codegen_tests/derive_struct_attribute_codegen_tests.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute_codegen_tests.rs index 8ed6c383..8e38ab91 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/derive_struct_attribute_codegen_tests.rs @@ -2,6 +2,7 @@ use super::{CodegenTest, ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode} use proc_macro2::TokenStream; use quote::quote; +/// Verify that we can derive the `Copy` trait on a transparent struct. mod derive_copy_struct { use super::*; @@ -10,7 +11,7 @@ mod derive_copy_struct { #[swift_bridge::bridge] mod ffi { #[swift_bridge(swift_repr = "struct")] - #[derive(Copy, Clone)] + #[derive(Copy)] struct SomeStruct { field: u8, } @@ -20,7 +21,7 @@ mod derive_copy_struct { fn expected_rust_tokens() -> ExpectedRustTokens { ExpectedRustTokens::ContainsMany(vec![quote! { - #[derive(Copy, Clone)] + #[derive(Copy)] pub struct SomeStruct { pub field: u8 } @@ -47,6 +48,7 @@ mod derive_copy_struct { } } +/// Verify that we can derive the `Clone` trait on a transparent struct. mod derive_clone_struct { use super::*; From f531c291ae100d5a70d08943913bcfede7e301e3 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 04:26:13 +0000 Subject: [PATCH 10/14] Adding extra tests --- .../codegen_tests/vec_codegen_tests.rs | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs index 3a16f6d0..f260cfcf 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs @@ -824,6 +824,210 @@ void* __swift_bridge__$Vec_SomeStruct$as_ptr(void* vec_ptr); } } +mod transparent_struct_vec_support_without_derives { + use super::*; + + fn bridge_module_tokens() -> TokenStream { + quote! { + mod ffi { + #[swift_bridge(swift_repr = "struct")] + struct SomeStruct { + integer: i64, + } + } + } + } + + fn expected_rust_tokens() -> ExpectedRustTokens { + ExpectedRustTokens::ContainsManyAndDoesNotContainMany { + contains: vec![], + does_not_contain: vec![ + quote! { + const _: () = { + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$new"] + pub extern "C" fn _new() -> *mut Vec { + Box::into_raw(Box::new(Vec::new())) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$drop"] + pub extern "C" fn _drop(vec: *mut Vec) { + let vec = unsafe { Box::from_raw(vec) }; + drop(vec) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$len"] + pub extern "C" fn _len(vec: *const Vec) -> usize { + unsafe { &*vec }.len() + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get"] + pub extern "C" fn _get(vec: *const Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &*vec }; + let val = vec.get(index).map(|v| *v ); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get_mut"] + pub extern "C" fn _get_mut(vec: *mut Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.get_mut(index).map(|v| *v ); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$push"] + pub extern "C" fn _push(vec: *mut Vec, val: __swift_bridge__SomeStruct) { + unsafe { &mut *vec }.push(val.into_rust_repr()) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$pop"] + pub extern "C" fn _pop(vec: *mut Vec) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.pop(); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$as_ptr"] + pub extern "C" fn _as_ptr(vec: *const Vec) -> *const SomeStruct { + unsafe { & *vec }.as_ptr() + } + } + }, + quote! { + const _: () = { + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$new"] + pub extern "C" fn _new() -> *mut Vec { + Box::into_raw(Box::new(Vec::new())) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$drop"] + pub extern "C" fn _drop(vec: *mut Vec) { + let vec = unsafe { Box::from_raw(vec) }; + drop(vec) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$len"] + pub extern "C" fn _len(vec: *const Vec) -> usize { + unsafe { &*vec }.len() + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get"] + pub extern "C" fn _get(vec: *const Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &*vec }; + let val = vec.get(index).map(|v| v.clone() ); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$get_mut"] + pub extern "C" fn _get_mut(vec: *mut Vec, index: usize) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.get_mut(index).map(|v| v.clone() ); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$push"] + pub extern "C" fn _push(vec: *mut Vec, val: __swift_bridge__SomeStruct) { + unsafe { &mut *vec }.push(val.into_rust_repr()) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$pop"] + pub extern "C" fn _pop(vec: *mut Vec) -> __swift_bridge__Option_SomeStruct { + let vec = unsafe { &mut *vec }; + let val = vec.pop(); + __swift_bridge__Option_SomeStruct::from_rust_repr(val) + } + + #[doc(hidden)] + #[export_name = "__swift_bridge__$Vec_SomeStruct$as_ptr"] + pub extern "C" fn _as_ptr(vec: *const Vec) -> *const SomeStruct { + unsafe { & *vec }.as_ptr() + } + } + }, + ] + } + } + + fn expected_swift_code() -> ExpectedSwiftCode { + ExpectedSwiftCode::DoesNotContainAfterTrim( + r#" +extension SomeStruct: Vectorizable { + public static func vecOfSelfNew() -> UnsafeMutableRawPointer { + __swift_bridge__$Vec_SomeStruct$new() + } + + public static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) { + __swift_bridge__$Vec_SomeStruct$drop(vecPtr) + } + + public static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) { + __swift_bridge__$Vec_SomeStruct$push(vecPtr, value.intoFfiRepr()) + } + + public static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$pop(vecPtr) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$get(vecPtr, index) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional { + let maybeStruct = __swift_bridge__$Vec_SomeStruct$get_mut(vecPtr, index) + return maybeStruct.intoSwiftRepr() + } + + public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt { + __swift_bridge__$Vec_SomeStruct$len(vecPtr) + } +} +"#, + ) + } + + fn expected_c_header() -> ExpectedCHeader { + ExpectedCHeader::DoesNotContainAfterTrim( + r#" +void* __swift_bridge__$Vec_SomeStruct$new(void); +void __swift_bridge__$Vec_SomeStruct$drop(void* vec_ptr); +void __swift_bridge__$Vec_SomeStruct$push(void* vec_ptr, __swift_bridge__$SomeStruct item); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$pop(void* vec_ptr); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$get(void* vec_ptr, uintptr_t index); +__swift_bridge__$Option$SomeStruct __swift_bridge__$Vec_SomeStruct$get_mut(void* vec_ptr, uintptr_t index); +uintptr_t __swift_bridge__$Vec_SomeStruct$len(void* vec_ptr); +void* __swift_bridge__$Vec_SomeStruct$as_ptr(void* vec_ptr); +"#, + ) + } + + #[test] + fn transparent_struct_vec_support_without_derives() { + 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 where T is a transparent struct. mod extern_rust_fn_return_vec_of_transparent_struct { use super::*; From b1e043feaeaa684039a41c252d562dab83abe0f3 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 04:28:02 +0000 Subject: [PATCH 11/14] cargo fmt --- crates/swift-bridge-ir/src/parse/parse_struct.rs | 2 +- crates/swift-integration-tests/src/struct_attributes.rs | 2 +- crates/swift-integration-tests/src/struct_attributes/derive.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/swift-bridge-ir/src/parse/parse_struct.rs b/crates/swift-bridge-ir/src/parse/parse_struct.rs index a14c462a..fee73664 100644 --- a/crates/swift-bridge-ir/src/parse/parse_struct.rs +++ b/crates/swift-bridge-ir/src/parse/parse_struct.rs @@ -116,7 +116,7 @@ impl<'a> SharedStructDeclarationParser<'a> { attribs.swift_repr = Some((StructSwiftRepr::Structure, val)); } StructAttrParseError::UnrecognizedAttribute(attribute) => { - self.errors.push(ParseError::StructUnrecognizedAttribute { + self.errors.push(ParseError::StructUnrecognizedAttribute { attribute, }); } diff --git a/crates/swift-integration-tests/src/struct_attributes.rs b/crates/swift-integration-tests/src/struct_attributes.rs index 61d040d2..eb0571b4 100644 --- a/crates/swift-integration-tests/src/struct_attributes.rs +++ b/crates/swift-integration-tests/src/struct_attributes.rs @@ -1,3 +1,3 @@ mod already_declared; -mod swift_name; mod derive; +mod swift_name; diff --git a/crates/swift-integration-tests/src/struct_attributes/derive.rs b/crates/swift-integration-tests/src/struct_attributes/derive.rs index 68f99572..1ca11403 100644 --- a/crates/swift-integration-tests/src/struct_attributes/derive.rs +++ b/crates/swift-integration-tests/src/struct_attributes/derive.rs @@ -25,4 +25,4 @@ mod ffi { struct StructDeriveClone3 { field: String, } -} \ No newline at end of file +} From e401f325e6c12cd57fd5763d9c44edf347a7b134 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 04:30:37 +0000 Subject: [PATCH 12/14] cargo fmt --- .../codegen_tests/vec_codegen_tests.rs | 2 +- .../src/codegen/generate_c_header.rs | 13 +++--- .../generate_rust_tokens/shared_struct.rs | 4 +- .../vec/vec_of_transparent_struct.rs | 41 +++++++++++-------- .../codegen/generate_swift/shared_struct.rs | 6 ++- 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs index f260cfcf..3e14bca0 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs @@ -958,7 +958,7 @@ mod transparent_struct_vec_support_without_derives { } } }, - ] + ], } } diff --git a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs index dbf96550..7dfa2424 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_c_header.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_c_header.rs @@ -2,8 +2,8 @@ use crate::bridged_type::shared_struct::StructField; use crate::bridged_type::{BridgeableType, BridgedType, StdLibType, StructFields}; -use crate::codegen::CodegenConfig; use crate::codegen::generate_rust_tokens::can_generate_vec_of_transparent_struct_functions; +use crate::codegen::CodegenConfig; use crate::parse::{SharedTypeDeclaration, TypeDeclaration, TypeDeclarations}; use crate::parsed_extern_fn::ParsedExternFn; use crate::{SwiftBridgeModule, SWIFT_BRIDGE_PREFIX}; @@ -113,11 +113,12 @@ impl SwiftBridgeModule { "".to_string() }; - let vec_support = if can_generate_vec_of_transparent_struct_functions(&ty_struct) { - vec_transparent_struct_c_support(&name) - } else { - format!("") - }; + let vec_support = + if can_generate_vec_of_transparent_struct_functions(&ty_struct) { + vec_transparent_struct_c_support(&name) + } else { + format!("") + }; let ty_decl = format!( r#"typedef struct {prefix}${name} {{{maybe_fields}}} {prefix}${name}; diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs index 2fa112ef..9bba0340 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_struct.rs @@ -2,8 +2,10 @@ //! crates/swift-bridge-ir/src/codegen/codegen_tests/shared_struct_codegen_tests.rs use crate::bridged_type::{BridgedType, SharedStruct}; +use crate::codegen::generate_rust_tokens::vec::vec_of_transparent_struct::{ + can_generate_vec_of_transparent_struct_functions, generate_vec_of_transparent_struct_functions, +}; use crate::{SwiftBridgeModule, SWIFT_BRIDGE_PREFIX}; -use crate::codegen::generate_rust_tokens::vec::vec_of_transparent_struct::{generate_vec_of_transparent_struct_functions, can_generate_vec_of_transparent_struct_functions}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::Ident; diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs index feeb3103..2afb12c5 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs @@ -1,18 +1,17 @@ use crate::bridged_type::{SharedStruct, StructSwiftRepr}; -use proc_macro2::{TokenStream}; +use proc_macro2::TokenStream; use quote::quote; - /// Generate the functions that Swift calls uses inside of the corresponding class for a /// transparent struct's Vectorizable implementation. /// /// So inside of `extension SomeTransparentStruct: Vectorizable {}` on the Swift side. pub(in super::super) fn generate_vec_of_transparent_struct_functions( - shared_struct: &SharedStruct + shared_struct: &SharedStruct, ) -> TokenStream { if can_generate_vec_of_transparent_struct_functions(&shared_struct) { let struct_name = &shared_struct.name; - + // examples: // "__swift_bridge__$Vec_SomeTransparentStruct$new" // "__swift_bridge__$Vec_SomeTransparentStruct$drop" @@ -31,7 +30,7 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( let export_name_push = make_export_name("push"); let export_name_pop = make_export_name("pop"); let export_name_as_ptr = make_export_name("as_ptr"); - + let ffi_struct_repr = &shared_struct.ffi_name_tokens(); let ffi_option_struct_repr = shared_struct.ffi_option_name_tokens(); // TODO: Check for trait implementation as well @@ -40,7 +39,7 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( } else { quote! { v.clone() } }; - + quote! { const _: () = { #[doc(hidden)] @@ -48,20 +47,20 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( pub extern "C" fn _new() -> *mut Vec<#struct_name> { Box::into_raw(Box::new(Vec::new())) } - + #[doc(hidden)] #[export_name = #export_name_drop] pub extern "C" fn _drop(vec: *mut Vec<#struct_name>) { let vec = unsafe { Box::from_raw(vec) }; drop(vec) } - + #[doc(hidden)] #[export_name = #export_name_len] pub extern "C" fn _len(vec: *const Vec<#struct_name>) -> usize { unsafe { &*vec }.len() } - + #[doc(hidden)] #[export_name = #export_name_get] pub extern "C" fn _get(vec: *const Vec<#struct_name>, index: usize) -> #ffi_option_struct_repr { @@ -69,7 +68,7 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( let val = vec.get(index).map(|v|#vec_map); #ffi_option_struct_repr::from_rust_repr(val) } - + #[doc(hidden)] #[export_name = #export_name_get_mut] pub extern "C" fn _get_mut(vec: *mut Vec<#struct_name>, index: usize) -> #ffi_option_struct_repr { @@ -77,13 +76,13 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( let val = vec.get_mut(index).map(|v|#vec_map); #ffi_option_struct_repr::from_rust_repr(val) } - + #[doc(hidden)] #[export_name = #export_name_push] pub extern "C" fn _push(vec: *mut Vec<#struct_name>, val: #ffi_struct_repr) { unsafe { &mut *vec }.push( val.into_rust_repr() ) } - + #[doc(hidden)] #[export_name = #export_name_pop] pub extern "C" fn _pop(vec: *mut Vec<#struct_name>) -> #ffi_option_struct_repr { @@ -91,7 +90,7 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( let val = vec.pop(); #ffi_option_struct_repr::from_rust_repr(val) } - + #[doc(hidden)] #[export_name = #export_name_as_ptr] pub extern "C" fn _as_ptr(vec: *const Vec<#struct_name>) -> *const #struct_name { @@ -104,7 +103,9 @@ pub(in super::super) fn generate_vec_of_transparent_struct_functions( } } -pub(crate) fn can_generate_vec_of_transparent_struct_functions(shared_struct: &SharedStruct) -> bool { +pub(crate) fn can_generate_vec_of_transparent_struct_functions( + shared_struct: &SharedStruct, +) -> bool { match shared_struct.swift_repr { StructSwiftRepr::Class => false, // TODO: Check for trait implementation as well @@ -115,9 +116,9 @@ pub(crate) fn can_generate_vec_of_transparent_struct_functions(shared_struct: &S #[cfg(test)] mod tests { use super::*; + use crate::bridged_type::{shared_struct::StructDerives, StructFields, StructSwiftRepr}; use crate::test_utils::assert_tokens_eq; use proc_macro2::{Ident, Span}; - use crate::bridged_type::{StructSwiftRepr, StructFields, shared_struct::StructDerives}; /// 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 @@ -189,7 +190,10 @@ mod tests { fields: StructFields::Named(vec![]), swift_name: None, already_declared: false, - derives: StructDerives { copy: true, clone: true }, + derives: StructDerives { + copy: true, + clone: true, + }, }; assert_tokens_eq( &generate_vec_of_transparent_struct_functions(&shared_struct), @@ -267,7 +271,10 @@ mod tests { fields: StructFields::Named(vec![]), swift_name: None, already_declared: false, - derives: StructDerives { copy: false, clone: true }, + derives: StructDerives { + copy: false, + clone: true, + }, }; assert_tokens_eq( &generate_vec_of_transparent_struct_functions(&shared_struct), diff --git a/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs index a90bfde4..f6521ccd 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_swift/shared_struct.rs @@ -48,7 +48,9 @@ impl SwiftBridgeModule { let convert_ffi_repr_to_swift = shared_struct.convert_ffi_expression_to_swift("self", &self.types); - let vectorizable_impl = if can_generate_vec_of_transparent_struct_functions(&shared_struct) { + let vectorizable_impl = if can_generate_vec_of_transparent_struct_functions( + &shared_struct, + ) { format!( r#" extension {struct_name}: Vectorizable {{ @@ -86,7 +88,7 @@ extension {struct_name}: Vectorizable {{ struct_name = struct_name ) } else { - format!("") + format!("") }; // No need to generate any code. Swift will automatically generate a From b08764eb93e9627ea61db3036e2f6e4a672e2072 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Thu, 16 Mar 2023 22:23:10 +0000 Subject: [PATCH 13/14] Adjusting test comments --- .../VecTests.swift | 23 ++++--------------- .../codegen_tests/vec_codegen_tests.rs | 8 +++++-- .../vec/vec_of_transparent_struct.rs | 12 +++++----- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift index ed01bebc..a73c91f1 100644 --- a/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift +++ b/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/VecTests.swift @@ -93,23 +93,8 @@ class VecTests: XCTestCase { XCTAssertEqual(reflected.pop()!, TransparentEnumInsideVecT.VariantB) } - /// Verify that a Vec of transparent struct can be used as an argument and return - /// type for extern "Rust" functions. - func testReflectVecOfTransparentStruct() throws { - let vec: RustVec = RustVec() - vec.push(value: TransparentStructInsideVecT(string: "string".intoRustString(), integer: 10)) - - let reflected = rust_reflect_vec_transparent_struct(vec) - XCTAssertEqual(reflected.len(), 1) - XCTAssertEqual(reflected.get(index: 0)!.string.toString(), "string") - XCTAssertEqual(reflected.get(index: 0)!.integer, 10) - let popped = try XCTUnwrap(reflected.pop()) - XCTAssertEqual(popped.string.toString(), "string") - XCTAssertEqual(popped.integer, 10) - } - - /// Verify that a Vec of transparent struct can be used as an argument and return - /// type for extern "Rust" functions. + /// Verify that a Vec of transparent struct with derive(Copy, Clone) can be used + /// as an argument and return type for extern "Rust" functions. func testReflectVecOfTransparentStructFromCopy() throws { let vec: RustVec = RustVec() vec.push(value: TransparentStructInsideVecTWithCopy(integer: 10)) @@ -121,8 +106,8 @@ class VecTests: XCTestCase { XCTAssertEqual(popped.integer, 10) } - /// Verify that a Vec of transparent struct can be used as an argument and return - /// type for extern "Rust" functions. + /// Verify that a Vec of transparent struct with derive(Clone) can be used as an + /// argument and return type for extern "Rust" functions. func testReflectVecOfTransparentStructFromClone() throws { let vec: RustVec = RustVec() vec.push(value: TransparentStructInsideVecT(string: "string".intoRustString(), integer: 10)) diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs index 3e14bca0..3eb31233 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs @@ -537,8 +537,8 @@ void __swift_bridge__$some_function(void* arg); } } -/// Verify that we emit Rust, Swift and C header code that allows a transparent struct be used -/// within a Vec. +/// Verify that we emit Rust, Swift and C header code that allows +/// a transparent struct with derive(Clone) to be used within a Vec. mod transparent_struct_vec_support { use super::*; @@ -682,6 +682,8 @@ void* __swift_bridge__$Vec_SomeStruct$as_ptr(void* vec_ptr); } } +/// Verify that we emit Rust, Swift and C header code that allows +/// a transparent struct with derive(Copy, Clone) to be used within a Vec. mod transparent_struct_vec_support_with_copy { use super::*; @@ -824,6 +826,8 @@ void* __swift_bridge__$Vec_SomeStruct$as_ptr(void* vec_ptr); } } +/// Verify that we DON'T emit Rust, Swift and C header code for a transparent +/// struct with no derived traits. mod transparent_struct_vec_support_without_derives { use super::*; diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs index 2afb12c5..e6976700 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs @@ -120,9 +120,9 @@ mod tests { use crate::test_utils::assert_tokens_eq; 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 - /// side. + /// Verify that we can generate the functions for shared struct with derive(Copy, Clone) that + /// gets exposed to Swift in order to power the `extension MyRustType: Vectorizable { }` + /// implementation on the Swift side. #[test] fn generates_vectorizable_impl_for_shared_struct_with_copy() { let expected = quote! { @@ -201,9 +201,9 @@ mod tests { ); } - /// 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 - /// side. + /// Verify that we can generate the functions for shared struct with derive(Clone) that + /// gets exposed to Swift in order to power the `extension MyRustType: Vectorizable { }` + /// implementation on the Swift side. #[test] fn generates_vectorizable_impl_for_shared_struct_with_clone() { let expected = quote! { From d7e3aeb3c8abff49221751bee9590e9ae9444039 Mon Sep 17 00:00:00 2001 From: Rodrigo Kreutz <8869678+rkreutz@users.noreply.github.com> Date: Sun, 9 Apr 2023 05:55:57 +0100 Subject: [PATCH 14/14] Adding compatibility feature flag --- Cargo.toml | 3 +++ crates/swift-bridge-ir/Cargo.toml | 5 +++++ .../src/codegen/codegen_tests/vec_codegen_tests.rs | 2 ++ .../generate_rust_tokens/vec/vec_of_transparent_struct.rs | 4 ++++ crates/swift-bridge-macro/Cargo.toml | 5 +++++ crates/swift-integration-tests/Cargo.toml | 2 +- 6 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 563a7016..4d07ea75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,9 @@ default = [] # Enables bridging of async functions. async = ["tokio", "once_cell"] +# Prioritises compatibility over performance +compatibility = ["swift-bridge-macro/compatibility"] + [build-dependencies] swift-bridge-build = {version = "0.1.51", path = "crates/swift-bridge-build"} diff --git a/crates/swift-bridge-ir/Cargo.toml b/crates/swift-bridge-ir/Cargo.toml index e99f4a32..ec3c16ca 100644 --- a/crates/swift-bridge-ir/Cargo.toml +++ b/crates/swift-bridge-ir/Cargo.toml @@ -7,6 +7,11 @@ description = "Holds the data structures and logic for bridge module parsing and repository = "https://github.com/chinedufn/swift-bridge" license = "Apache-2.0/MIT" +[features] + +# Prioritises compatibility over performance +compatibility = [] + [dependencies] proc-macro2 = "1" quote = "1" diff --git a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs index 3eb31233..15c4bc16 100644 --- a/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs +++ b/crates/swift-bridge-ir/src/codegen/codegen_tests/vec_codegen_tests.rs @@ -539,6 +539,7 @@ void __swift_bridge__$some_function(void* arg); /// Verify that we emit Rust, Swift and C header code that allows /// a transparent struct with derive(Clone) to be used within a Vec. +#[cfg(feature = "compatibility")] mod transparent_struct_vec_support { use super::*; @@ -1033,6 +1034,7 @@ void* __swift_bridge__$Vec_SomeStruct$as_ptr(void* vec_ptr); } /// Test code generation for Rust function that returns a Vec where T is a transparent struct. +#[cfg(feature = "compatibility")] mod extern_rust_fn_return_vec_of_transparent_struct { use super::*; diff --git a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs index e6976700..5afe3396 100644 --- a/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs +++ b/crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_struct.rs @@ -109,7 +109,10 @@ pub(crate) fn can_generate_vec_of_transparent_struct_functions( match shared_struct.swift_repr { StructSwiftRepr::Class => false, // TODO: Check for trait implementation as well + #[cfg(feature = "compatibility")] StructSwiftRepr::Structure => shared_struct.derives.copy || shared_struct.derives.clone, + #[cfg(not(feature = "compatibility"))] + StructSwiftRepr::Structure => shared_struct.derives.copy, } } @@ -205,6 +208,7 @@ mod tests { /// gets exposed to Swift in order to power the `extension MyRustType: Vectorizable { }` /// implementation on the Swift side. #[test] + #[cfg(feature = "compatibility")] fn generates_vectorizable_impl_for_shared_struct_with_clone() { let expected = quote! { const _: () = { diff --git a/crates/swift-bridge-macro/Cargo.toml b/crates/swift-bridge-macro/Cargo.toml index 291d3839..32c61569 100644 --- a/crates/swift-bridge-macro/Cargo.toml +++ b/crates/swift-bridge-macro/Cargo.toml @@ -10,6 +10,11 @@ license = "Apache-2.0/MIT" [lib] proc-macro = true +[features] + +# Prioritises compatibility over performance +compatibility = ["swift-bridge-ir/compatibility"] + [dependencies] proc-macro2 = "1" quote = "1" diff --git a/crates/swift-integration-tests/Cargo.toml b/crates/swift-integration-tests/Cargo.toml index 23aa0323..fe9a9929 100644 --- a/crates/swift-integration-tests/Cargo.toml +++ b/crates/swift-integration-tests/Cargo.toml @@ -19,4 +19,4 @@ crate-type = ["staticlib"] swift-bridge-build = {path = "../swift-bridge-build"} [dependencies] -swift-bridge = {path = "../../", features = ["async"]} +swift-bridge = {path = "../../", features = ["async", "compatibility"]}