Skip to content

Commit 53b93f4

Browse files
authored
Support derive(Debug) on enums (#194)
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") ```
1 parent 25b1ea0 commit 53b93f4

File tree

18 files changed

+307
-94
lines changed

18 files changed

+307
-94
lines changed

SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedEnumAttributeTests.swift

+7
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,12 @@ class SharedEnumAttributeTests: XCTestCase {
3030
AlreadyDeclaredEnumTest.Variant
3131
)
3232
}
33+
34+
35+
/// Verify that we can use the generated Debug impl.
36+
func testSharedEnumDeriveDebug() throws {
37+
let debugString = String(reflecting: DeriveDebugEnum.Variant)
38+
XCTAssertEqual(debugString, "Variant")
39+
}
3340
}
3441

SwiftRustIntegrationTestRunner/build-rust.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then
1515
export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}"
1616
fi
1717

18-
cd $PROJECT_DIR
18+
cd "$PROJECT_DIR"
1919

2020
if [[ $CONFIGURATION == "Release" ]]; then
2121
echo "BUIlDING FOR RELEASE"

crates/swift-bridge-ir/src/bridged_type.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::bridged_type::built_in_tuple::BuiltInTuple;
1717
use crate::parse::{HostLang, TypeDeclaration, TypeDeclarations};
1818

1919
use self::bridged_option::BridgedOption;
20-
pub(crate) use self::shared_enum::{EnumVariant, SharedEnum};
20+
pub(crate) use self::shared_enum::{DeriveAttrs, EnumVariant, SharedEnum};
2121
pub(crate) use self::shared_struct::{SharedStruct, StructFields, StructSwiftRepr};
2222

2323
pub(crate) mod boxed_fn;

crates/swift-bridge-ir/src/bridged_type/shared_enum.rs

+6
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ pub(crate) use self::enum_variant::EnumVariant;
99

1010
use super::StructFields;
1111

12+
#[derive(Default, Clone)]
13+
pub(crate) struct DeriveAttrs {
14+
pub debug: bool,
15+
}
16+
1217
#[derive(Clone)]
1318
pub(crate) struct SharedEnum {
1419
pub name: Ident,
1520
pub variants: Vec<EnumVariant>,
1621
pub already_declared: bool,
1722
pub swift_name: Option<LitStr>,
23+
pub derive: DeriveAttrs,
1824
}
1925

2026
impl SharedEnum {

crates/swift-bridge-ir/src/codegen/codegen_tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod async_function_codegen_tests;
3333
mod boxed_fnonce_codegen_tests;
3434
mod built_in_tuple_codegen_tests;
3535
mod conditional_compilation_codegen_tests;
36+
mod derive_attribute_codegen_tests;
3637
mod derive_struct_attribute_codegen_tests;
3738
mod extern_rust_function_opaque_rust_type_argument_codegen_tests;
3839
mod extern_rust_function_opaque_rust_type_return_codegen_tests;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use super::{CodegenTest, ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode};
2+
use proc_macro2::TokenStream;
3+
use quote::quote;
4+
5+
/// Verify that we generate debugDescription in Swift and Debug function in Rust when using #\[derive(Debug)]
6+
mod derive_debug_enum {
7+
use super::*;
8+
9+
fn bridge_module_tokens() -> TokenStream {
10+
quote! {
11+
#[swift_bridge::bridge]
12+
mod ffi {
13+
#[derive(Debug)]
14+
enum SomeEnum {
15+
Variant1
16+
}
17+
}
18+
}
19+
}
20+
21+
fn expected_rust_tokens() -> ExpectedRustTokens {
22+
ExpectedRustTokens::ContainsMany(vec![
23+
quote! {
24+
#[derive(Copy, Clone, ::std::fmt::Debug)]
25+
pub enum SomeEnum {
26+
Variant1
27+
}
28+
},
29+
quote! {
30+
#[export_name = "__swift_bridge__$SomeEnum$Debug"]
31+
pub extern "C" fn __swift_bridge__SomeEnum_Debug(this: __swift_bridge__SomeEnum) -> *mut swift_bridge::string::RustString {
32+
swift_bridge::string::RustString(format!("{:?}", this.into_rust_repr())).box_into_raw()
33+
}
34+
},
35+
])
36+
}
37+
38+
fn expected_swift_code() -> ExpectedSwiftCode {
39+
ExpectedSwiftCode::ContainsAfterTrim(
40+
r#"
41+
extension SomeEnum: CustomDebugStringConvertible {
42+
public var debugDescription: String {
43+
RustString(ptr: __swift_bridge__$SomeEnum$Debug(self.intoFfiRepr())).toString()
44+
}
45+
}
46+
"#,
47+
)
48+
}
49+
50+
fn expected_c_header() -> ExpectedCHeader {
51+
ExpectedCHeader::ContainsAfterTrim(
52+
r#"
53+
void* __swift_bridge__$SomeEnum$Debug(__swift_bridge__$SomeEnum this);
54+
"#,
55+
)
56+
}
57+
58+
#[test]
59+
fn generates_enum_to_and_from_ffi_conversions_no_data() {
60+
CodegenTest {
61+
bridge_module: bridge_module_tokens().into(),
62+
expected_rust_tokens: expected_rust_tokens(),
63+
expected_swift_code: expected_swift_code(),
64+
expected_c_header: expected_c_header(),
65+
}
66+
.test();
67+
}
68+
}

crates/swift-bridge-ir/src/codegen/generate_c_header.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
152152
variants += &variant;
153153
}
154154

155+
let derive_debug_impl = if ty_enum.derive.debug {
156+
format!("void* {ffi_name}$Debug({ffi_name} this);")
157+
} else {
158+
"".to_string()
159+
};
160+
155161
let maybe_vec_support = if ty_enum.has_one_or_more_variants_with_data() {
156162
"".to_string()
157163
} else {
@@ -162,7 +168,8 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
162168
let enum_decl = format!(
163169
r#"typedef enum {ffi_tag_name} {{ {variants}}} {ffi_tag_name};
164170
typedef struct {ffi_name} {{ {ffi_tag_name} tag; }} {ffi_name};
165-
typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};{maybe_vec_support}"#,
171+
typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};
172+
{derive_debug_impl}{maybe_vec_support}"#,
166173
ffi_name = ffi_name,
167174
ffi_tag_name = ffi_tag_name,
168175
option_ffi_name = option_ffi_name,
@@ -223,7 +230,8 @@ typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi
223230
r#"{variant_fields}union {ffi_union_name} {union_fields};
224231
typedef enum {ffi_tag_name} {{ {variants}}} {ffi_tag_name};
225232
typedef struct {ffi_name} {{ {ffi_tag_name} tag; union {ffi_union_name} payload;}} {ffi_name};
226-
typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};{maybe_vec_support}"#,
233+
typedef struct {option_ffi_name} {{ bool is_some; {ffi_name} val; }} {option_ffi_name};
234+
{derive_debug_impl}{maybe_vec_support}"#,
227235
union_fields = ffi_union_field_names,
228236
variant_fields = variant_fields,
229237
ffi_name = ffi_name,

crates/swift-bridge-ir/src/codegen/generate_rust_tokens/shared_enum.rs

+26-4
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,34 @@ impl SwiftBridgeModule {
130130
convert_ffi_variants_to_rust.push(convert_ffi_variant_to_rust);
131131
}
132132

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

140+
// User derives
141+
let mut derive_impl_ffi_bridges = vec![];
142+
143+
// We currently only allow derive(Debug) on non data carrying enums in order
144+
// to prevent a potential memory safety issue.
145+
// https://github.com/chinedufn/swift-bridge/pull/194#discussion_r1134386788
146+
if shared_enum.derive.debug && !shared_enum.has_one_or_more_variants_with_data() {
147+
derives.push(quote! {::std::fmt::Debug});
148+
149+
// __swift_bridge__$SomeEnum$Debug
150+
let export_name = format!("{}$Debug", shared_enum.ffi_name_string());
151+
// __swift_bridge__SomeEnum_Debug
152+
let fn_name = format_ident!("{}_Debug", enum_ffi_name);
153+
derive_impl_ffi_bridges.push(quote! {
154+
#[export_name = #export_name]
155+
pub extern "C" fn #fn_name(this: #enum_ffi_name) -> *mut swift_bridge::string::RustString {
156+
swift_bridge::string::RustString(format!("{:?}", this.into_rust_repr())).box_into_raw()
157+
}
158+
});
159+
}
160+
141161
let vec_support = if shared_enum.has_one_or_more_variants_with_data() {
142162
// Enums with variants that contain data are not yet supported.
143163
quote! {}
@@ -146,7 +166,7 @@ impl SwiftBridgeModule {
146166
};
147167

148168
let definition = quote! {
149-
#[derive(#(#automatic_derives),*)]
169+
#[derive(#(#derives),*)]
150170
pub enum #enum_name {
151171
#(#enum_variants),*
152172
}
@@ -217,6 +237,8 @@ impl SwiftBridgeModule {
217237
}
218238

219239
#vec_support
240+
241+
#(#derive_impl_ffi_bridges),*
220242
};
221243

222244
Some(definition)

crates/swift-bridge-ir/src/codegen/generate_rust_tokens/vec/vec_of_transparent_enum.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub(in super::super) fn generate_vec_of_transparent_enum_functions(
9696
#[cfg(test)]
9797
mod tests {
9898
use super::*;
99-
use crate::test_utils::assert_tokens_eq;
99+
use crate::{bridged_type::DeriveAttrs, test_utils::assert_tokens_eq};
100100
use proc_macro2::{Ident, Span};
101101

102102
/// Verify that we can generate the functions for an opaque Rust type that get exposed to Swift
@@ -168,6 +168,7 @@ mod tests {
168168
variants: vec![],
169169
already_declared: false,
170170
swift_name: None,
171+
derive: DeriveAttrs::default(),
171172
};
172173
assert_tokens_eq(
173174
&generate_vec_of_transparent_enum_functions(&shared_enum),

crates/swift-bridge-ir/src/codegen/generate_swift/shared_enum.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,19 @@ extension {enum_name}: Vectorizable {{
130130
)
131131
};
132132

133+
let derive_debug_impl = if shared_enum.derive.debug {
134+
format!(
135+
r#"
136+
extension {enum_name}: CustomDebugStringConvertible {{
137+
public var debugDescription: String {{
138+
RustString(ptr: __swift_bridge__${enum_name}$Debug(self.intoFfiRepr())).toString()
139+
}}
140+
}}"#
141+
)
142+
} else {
143+
"".to_string()
144+
};
145+
133146
let swift_enum = format!(
134147
r#"public enum {enum_name} {{{variants}}}
135148
extension {enum_name} {{
@@ -159,7 +172,7 @@ extension {option_ffi_name} {{
159172
return {option_ffi_name}(is_some: false, val: {ffi_repr_name}())
160173
}}
161174
}}
162-
}}{vectorizable_impl}"#,
175+
}}{vectorizable_impl}{derive_debug_impl}"#,
163176
enum_name = enum_name,
164177
enum_ffi_name = enum_ffi_name,
165178
option_ffi_name = option_ffi_name,

crates/swift-bridge-ir/src/errors.rs

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ impl ParseErrors {
1414
self.errors.push(error);
1515
}
1616

17+
pub fn append(&mut self, errors: Vec<ParseError>) {
18+
for error in errors {
19+
self.push(error);
20+
}
21+
}
22+
1723
pub fn combine_all(mut self) -> Result<(), syn::Error> {
1824
if self.errors.len() == 0 {
1925
return Ok(());

0 commit comments

Comments
 (0)