Skip to content

Commit

Permalink
Support derive(Debug) on enums (#194)
Browse files Browse the repository at this point in the history
Related issue: #190 

Example:

```rs
// Rust

#[swift_bridge::bridge]
mod ffi {
    #[derive(Debug)]
    enum DeriveDebugEnum {
        Variant,
    }
}
```

```swift
// Swift

let debugString = String(reflecting: DeriveDebugEnum.Variant)
XCTAssertEqual(debugString, "Variant")
```
  • Loading branch information
amsam0 authored Apr 6, 2023
1 parent 25b1ea0 commit 53b93f4
Show file tree
Hide file tree
Showing 18 changed files with 307 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,12 @@ class SharedEnumAttributeTests: XCTestCase {
AlreadyDeclaredEnumTest.Variant
)
}


/// Verify that we can use the generated Debug impl.
func testSharedEnumDeriveDebug() throws {
let debugString = String(reflecting: DeriveDebugEnum.Variant)
XCTAssertEqual(debugString, "Variant")
}
}

2 changes: 1 addition & 1 deletion SwiftRustIntegrationTestRunner/build-rust.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then
export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}"
fi

cd $PROJECT_DIR
cd "$PROJECT_DIR"

if [[ $CONFIGURATION == "Release" ]]; then
echo "BUIlDING FOR RELEASE"
Expand Down
2 changes: 1 addition & 1 deletion crates/swift-bridge-ir/src/bridged_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::bridged_type::built_in_tuple::BuiltInTuple;
use crate::parse::{HostLang, TypeDeclaration, TypeDeclarations};

use self::bridged_option::BridgedOption;
pub(crate) use self::shared_enum::{EnumVariant, SharedEnum};
pub(crate) use self::shared_enum::{DeriveAttrs, EnumVariant, SharedEnum};
pub(crate) use self::shared_struct::{SharedStruct, StructFields, StructSwiftRepr};

pub(crate) mod boxed_fn;
Expand Down
6 changes: 6 additions & 0 deletions crates/swift-bridge-ir/src/bridged_type/shared_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ pub(crate) use self::enum_variant::EnumVariant;

use super::StructFields;

#[derive(Default, Clone)]
pub(crate) struct DeriveAttrs {
pub debug: bool,
}

#[derive(Clone)]
pub(crate) struct SharedEnum {
pub name: Ident,
pub variants: Vec<EnumVariant>,
pub already_declared: bool,
pub swift_name: Option<LitStr>,
pub derive: DeriveAttrs,
}

impl SharedEnum {
Expand Down
1 change: 1 addition & 0 deletions crates/swift-bridge-ir/src/codegen/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ mod async_function_codegen_tests;
mod boxed_fnonce_codegen_tests;
mod built_in_tuple_codegen_tests;
mod conditional_compilation_codegen_tests;
mod derive_attribute_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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use super::{CodegenTest, ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode};
use proc_macro2::TokenStream;
use quote::quote;

/// Verify that we generate debugDescription in Swift and Debug function in Rust when using #\[derive(Debug)]
mod derive_debug_enum {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
#[swift_bridge::bridge]
mod ffi {
#[derive(Debug)]
enum SomeEnum {
Variant1
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::ContainsMany(vec![
quote! {
#[derive(Copy, Clone, ::std::fmt::Debug)]
pub enum SomeEnum {
Variant1
}
},
quote! {
#[export_name = "__swift_bridge__$SomeEnum$Debug"]
pub extern "C" fn __swift_bridge__SomeEnum_Debug(this: __swift_bridge__SomeEnum) -> *mut swift_bridge::string::RustString {
swift_bridge::string::RustString(format!("{:?}", this.into_rust_repr())).box_into_raw()
}
},
])
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
extension SomeEnum: CustomDebugStringConvertible {
public var debugDescription: String {
RustString(ptr: __swift_bridge__$SomeEnum$Debug(self.intoFfiRepr())).toString()
}
}
"#,
)
}

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

#[test]
fn generates_enum_to_and_from_ffi_conversions_no_data() {
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();
}
}
12 changes: 10 additions & 2 deletions crates/swift-bridge-ir/src/codegen/generate_c_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
variants += &variant;
}

let derive_debug_impl = if ty_enum.derive.debug {
format!("void* {ffi_name}$Debug({ffi_name} this);")
} else {
"".to_string()
};

let maybe_vec_support = if ty_enum.has_one_or_more_variants_with_data() {
"".to_string()
} else {
Expand All @@ -162,7 +168,8 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
let enum_decl = format!(
r#"typedef enum {ffi_tag_name} {{ {variants}}} {ffi_tag_name};
typedef struct {ffi_name} {{ {ffi_tag_name} tag; }} {ffi_name};
typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};{maybe_vec_support}"#,
typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};
{derive_debug_impl}{maybe_vec_support}"#,
ffi_name = ffi_name,
ffi_tag_name = ffi_tag_name,
option_ffi_name = option_ffi_name,
Expand Down Expand Up @@ -223,7 +230,8 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
r#"{variant_fields}union {ffi_union_name} {union_fields};
typedef enum {ffi_tag_name} {{ {variants}}} {ffi_tag_name};
typedef struct {ffi_name} {{ {ffi_tag_name} tag; union {ffi_union_name} payload;}} {ffi_name};
typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};{maybe_vec_support}"#,
typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};
{derive_debug_impl}{maybe_vec_support}"#,
union_fields = ffi_union_field_names,
variant_fields = variant_fields,
ffi_name = ffi_name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,34 @@ impl SwiftBridgeModule {
convert_ffi_variants_to_rust.push(convert_ffi_variant_to_rust);
}

// TODO:
// Parse any derives that the user has specified and combine those with our auto derives.
let automatic_derives = if shared_enum.has_one_or_more_variants_with_data() {
// Auto derives
let mut derives = if shared_enum.has_one_or_more_variants_with_data() {
vec![]
} else {
vec![quote! {Copy}, quote! {Clone}]
};

// User derives
let mut derive_impl_ffi_bridges = vec![];

// We currently only allow derive(Debug) on non data carrying enums in order
// to prevent a potential memory safety issue.
// https://github.com/chinedufn/swift-bridge/pull/194#discussion_r1134386788
if shared_enum.derive.debug && !shared_enum.has_one_or_more_variants_with_data() {
derives.push(quote! {::std::fmt::Debug});

// __swift_bridge__$SomeEnum$Debug
let export_name = format!("{}$Debug", shared_enum.ffi_name_string());
// __swift_bridge__SomeEnum_Debug
let fn_name = format_ident!("{}_Debug", enum_ffi_name);
derive_impl_ffi_bridges.push(quote! {
#[export_name = #export_name]
pub extern "C" fn #fn_name(this: #enum_ffi_name) -> *mut swift_bridge::string::RustString {
swift_bridge::string::RustString(format!("{:?}", this.into_rust_repr())).box_into_raw()
}
});
}

let vec_support = if shared_enum.has_one_or_more_variants_with_data() {
// Enums with variants that contain data are not yet supported.
quote! {}
Expand All @@ -146,7 +166,7 @@ impl SwiftBridgeModule {
};

let definition = quote! {
#[derive(#(#automatic_derives),*)]
#[derive(#(#derives),*)]
pub enum #enum_name {
#(#enum_variants),*
}
Expand Down Expand Up @@ -217,6 +237,8 @@ impl SwiftBridgeModule {
}

#vec_support

#(#derive_impl_ffi_bridges),*
};

Some(definition)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub(in super::super) fn generate_vec_of_transparent_enum_functions(
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::assert_tokens_eq;
use crate::{bridged_type::DeriveAttrs, 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
Expand Down Expand Up @@ -168,6 +168,7 @@ mod tests {
variants: vec![],
already_declared: false,
swift_name: None,
derive: DeriveAttrs::default(),
};
assert_tokens_eq(
&generate_vec_of_transparent_enum_functions(&shared_enum),
Expand Down
15 changes: 14 additions & 1 deletion crates/swift-bridge-ir/src/codegen/generate_swift/shared_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ extension {enum_name}: Vectorizable {{
)
};

let derive_debug_impl = if shared_enum.derive.debug {
format!(
r#"
extension {enum_name}: CustomDebugStringConvertible {{
public var debugDescription: String {{
RustString(ptr: __swift_bridge__${enum_name}$Debug(self.intoFfiRepr())).toString()
}}
}}"#
)
} else {
"".to_string()
};

let swift_enum = format!(
r#"public enum {enum_name} {{{variants}}}
extension {enum_name} {{
Expand Down Expand Up @@ -159,7 +172,7 @@ extension {option_ffi_name} {{
return {option_ffi_name}(is_some: false, val: {ffi_repr_name}())
}}
}}
}}{vectorizable_impl}"#,
}}{vectorizable_impl}{derive_debug_impl}"#,
enum_name = enum_name,
enum_ffi_name = enum_ffi_name,
option_ffi_name = option_ffi_name,
Expand Down
6 changes: 6 additions & 0 deletions crates/swift-bridge-ir/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ impl ParseErrors {
self.errors.push(error);
}

pub fn append(&mut self, errors: Vec<ParseError>) {
for error in errors {
self.push(error);
}
}

pub fn combine_all(mut self) -> Result<(), syn::Error> {
if self.errors.len() == 0 {
return Ok(());
Expand Down
Loading

0 comments on commit 53b93f4

Please sign in to comment.