Skip to content

Commit 34ce1ff

Browse files
authored
Suppress dead_code warning when returning -> Result<*, TransparentType> (#278)
In Rust 1.79.0, dead code warnings are emitted from enums where the compiler does not infer that wrapped data is used, even when the enum has been annotated with `#[repr(C]`. This warning triggers in `swift-bridge` when defining transparent structs or enums that will be returned in error results. This appears to be a regression in the `dead_code` rustc lint. Pending upstream fixes to rustc, this change to `swift-bridge` annotates generated result enums with `#[allow(unused)]`. Fixes #270 ``` error: field `0` is never read | 1 | #[swift_bridge::bridge] | ----------------------- field in this variant ... 3 | enum ResultTransparentEnum { | ^^^^^^^^^^^^^^^^^^^^^ | help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field | 3 | enum () { | ~~ ``` ## Example bridge The following bridge code is the minimal reproduction case: ``` rust #[swift_bridge::bridge] mod ffi { #[swift_bridge(swift_repr = "struct")] struct TransparentErrorStruct(pub String); extern "Rust" { fn rust_func_returns_result_transparent_struct( succeed: bool ) -> Result<(), TransparentErrorStruct>; } } fn rust_func_returns_result_transparent_struct( succeed: bool ) -> Result<(), ffi::ResultTestTransparentStruct> { if succeed { Ok(()) } else { Err(ffi::ResultTestTransparentStruct("failed".to_string())) } } impl std::error::Error for ffi:: TransparentErrorStruct {} impl std::fmt::Debug for ffi:: TransparentErrorStruct { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self) } } impl std::fmt::Display for ffi:: TransparentErrorStruct { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } ```
1 parent 717fcef commit 34ce1ff

File tree

5 files changed

+178
-4
lines changed

5 files changed

+178
-4
lines changed

SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/Result.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ extension AsyncResultOpaqueRustType2: Error {}
3535
extension ResultTransparentEnum: @unchecked Sendable {}
3636
extension ResultTransparentEnum: Error {}
3737

38+
extension ResultTransparentStruct: @unchecked Sendable {}
39+
extension ResultTransparentStruct: Error {}
40+
3841
extension SameEnum: @unchecked Sendable {}
3942
extension SameEnum: Error {}
4043

SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,46 @@ class ResultTests: XCTestCase {
193193
}
194194
}
195195

196+
/// Verify that we can receive a Result<(), TransparentStruct> from Rust
197+
func testResultNullTransparentStruct() throws {
198+
try! rust_func_return_result_null_transparent_struct(true)
199+
200+
do {
201+
try rust_func_return_result_null_transparent_struct(false)
202+
XCTFail("The function should have returned an error.")
203+
} catch let error as ResultTransparentStruct {
204+
XCTAssertEqual(error.inner.toString(), "failed")
205+
}
206+
207+
XCTContext.runActivity(named: "Should return a Unit type") {
208+
_ in
209+
do {
210+
let _ :() = try rust_func_return_result_unit_type_enum_opaque_rust(true)
211+
} catch {
212+
XCTFail()
213+
}
214+
}
215+
216+
XCTContext.runActivity(named: "Should throw an error") {
217+
_ in
218+
do {
219+
let _ :() = try rust_func_return_result_unit_type_enum_opaque_rust(false)
220+
XCTFail("The function should have returned an error.")
221+
} catch let error as ResultTransparentEnum {
222+
switch error {
223+
case .NamedField(let data):
224+
XCTAssertEqual(data, 123)
225+
case .UnnamedFields(_, _):
226+
XCTFail()
227+
case .NoFields:
228+
XCTFail()
229+
}
230+
} catch {
231+
XCTFail()
232+
}
233+
}
234+
}
235+
196236
/// Verify that we can receive a Result<Vec<>, OpaqueRust> from Rust
197237
func testSwiftCallRustResultVecUInt32Rust() throws {
198238
let vec = try! rust_func_return_result_of_vec_u32()

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,14 @@ impl BuiltInResult {
375375
.err_ty
376376
.to_ffi_compatible_rust_type(swift_bridge_path, types);
377377
let mut custom_rust_ffi_types = vec![];
378+
// TODO: remove `#[allow(unused)]` when rustc no longer issues dead code warnings for `#[repr(C)]`
379+
// structs or enums: https://github.com/rust-lang/rust/issues/126706
378380
custom_rust_ffi_types.push(quote! {
379381
#[repr(C)]
380382
pub enum #ty {
383+
#[allow(unused)]
381384
Ok #ok,
385+
#[allow(unused)]
382386
Err(#err),
383387
}
384388
});

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

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,16 @@ mod extern_rust_fn_return_result_opaque_rust_type_and_transparent_enum_type {
407407
}
408408
}
409409

410+
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
411+
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
412+
// issue: https://github.com/rust-lang/rust/issues/126706
410413
fn expected_rust_tokens() -> ExpectedRustTokens {
411414
ExpectedRustTokens::Contains(quote! {
412415
#[repr(C)]
413416
pub enum ResultSomeOkTypeAndSomeErrEnum{
417+
#[allow(unused)]
414418
Ok(*mut super::SomeOkType),
419+
#[allow(unused)]
415420
Err(__swift_bridge__SomeErrEnum),
416421
}
417422

@@ -484,11 +489,16 @@ mod extern_rust_fn_return_result_transparent_enum_type_and_opaque_rust_type {
484489
}
485490
}
486491

492+
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
493+
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
494+
// issue: https://github.com/rust-lang/rust/issues/126706
487495
fn expected_rust_tokens() -> ExpectedRustTokens {
488496
ExpectedRustTokens::Contains(quote! {
489497
#[repr(C)]
490498
pub enum ResultSomeOkEnumAndSomeErrType{
499+
#[allow(unused)]
491500
Ok(__swift_bridge__SomeOkEnum),
501+
#[allow(unused)]
492502
Err(*mut super::SomeErrType),
493503
}
494504

@@ -558,11 +568,16 @@ mod extern_rust_fn_return_result_unit_type_and_transparent_enum_type {
558568
}
559569
}
560570

571+
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
572+
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
573+
// issue: https://github.com/rust-lang/rust/issues/126706
561574
fn expected_rust_tokens() -> ExpectedRustTokens {
562575
ExpectedRustTokens::Contains(quote! {
563576
#[repr(C)]
564577
pub enum ResultVoidAndSomeErrEnum{
578+
#[allow(unused)]
565579
Ok,
580+
#[allow(unused)]
566581
Err(__swift_bridge__SomeErrEnum),
567582
}
568583

@@ -628,12 +643,17 @@ mod extern_rust_fn_return_result_tuple_type_and_transparent_enum_type {
628643
}
629644
}
630645

646+
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
647+
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
648+
// issue: https://github.com/rust-lang/rust/issues/126706
631649
fn expected_rust_tokens() -> ExpectedRustTokens {
632650
ExpectedRustTokens::ContainsMany(vec![
633651
quote! {
634652
#[repr(C)]
635653
pub enum ResultTupleI32U32AndSomeErrEnum{
654+
#[allow(unused)]
636655
Ok(__swift_bridge__tuple_I32U32),
656+
#[allow(unused)]
637657
Err(__swift_bridge__SomeErrEnum),
638658
}
639659
},
@@ -687,3 +707,77 @@ typedef struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum{__swift_bridge__
687707
.test();
688708
}
689709
}
710+
711+
/// Test code generation for Rust function that returns a Result<T, E> where T is () and
712+
/// E is a transparent struct type.
713+
mod extern_rust_fn_return_result_unit_type_and_transparent_struct_type {
714+
use super::*;
715+
716+
fn bridge_module_tokens() -> TokenStream {
717+
quote! {
718+
mod ffi {
719+
struct SomeErrStruct {
720+
inner: String,
721+
}
722+
extern "Rust" {
723+
fn some_function() -> Result<(), SomeErrStruct>;
724+
}
725+
}
726+
}
727+
}
728+
729+
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
730+
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
731+
// issue: https://github.com/rust-lang/rust/issues/126706
732+
fn expected_rust_tokens() -> ExpectedRustTokens {
733+
ExpectedRustTokens::Contains(quote! {
734+
#[repr(C)]
735+
pub enum ResultVoidAndSomeErrStruct{
736+
#[allow(unused)]
737+
Ok,
738+
#[allow(unused)]
739+
Err(__swift_bridge__SomeErrStruct),
740+
}
741+
742+
#[export_name = "__swift_bridge__$some_function"]
743+
pub extern "C" fn __swift_bridge__some_function() -> ResultVoidAndSomeErrStruct{
744+
match super::some_function() {
745+
Ok(ok) => ResultVoidAndSomeErrStruct::Ok,
746+
Err(err) => ResultVoidAndSomeErrStruct::Err(err.into_ffi_repr()),
747+
}
748+
}
749+
})
750+
}
751+
752+
fn expected_swift_code() -> ExpectedSwiftCode {
753+
ExpectedSwiftCode::ContainsAfterTrim(
754+
r#"
755+
public func some_function() throws -> () {
756+
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultVoidAndSomeErrStruct$ResultOk: return case __swift_bridge__$ResultVoidAndSomeErrStruct$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } }()
757+
}
758+
"#,
759+
)
760+
}
761+
762+
fn expected_c_header() -> ExpectedCHeader {
763+
ExpectedCHeader::ContainsManyAfterTrim(vec![
764+
r#"
765+
typedef enum __swift_bridge__$ResultVoidAndSomeErrStruct$Tag {__swift_bridge__$ResultVoidAndSomeErrStruct$ResultOk, __swift_bridge__$ResultVoidAndSomeErrStruct$ResultErr} __swift_bridge__$ResultVoidAndSomeErrStruct$Tag;
766+
union __swift_bridge__$ResultVoidAndSomeErrStruct$Fields {struct __swift_bridge__$SomeErrStruct err;};
767+
typedef struct __swift_bridge__$ResultVoidAndSomeErrStruct{__swift_bridge__$ResultVoidAndSomeErrStruct$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrStruct$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrStruct;
768+
"#,
769+
r#"struct __swift_bridge__$ResultVoidAndSomeErrStruct __swift_bridge__$some_function(void)"#,
770+
])
771+
}
772+
773+
#[test]
774+
fn extern_rust_result_transparent_struct_type_and_opaque_rust_type() {
775+
CodegenTest {
776+
bridge_module: bridge_module_tokens().into(),
777+
expected_rust_tokens: expected_rust_tokens(),
778+
expected_swift_code: expected_swift_code(),
779+
expected_c_header: expected_c_header(),
780+
}
781+
.test();
782+
}
783+
}

crates/swift-integration-tests/src/result.rs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
//! See also: crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs
2-
// This is a temporary workaround until https://github.com/chinedufn/swift-bridge/issues/270
3-
// is closed. When tests are compiled they have `-D warnings` (deny warnings) enabled, so
4-
// tests won't even compile unless this warning is ignored.
5-
#![allow(dead_code)]
62
73
#[swift_bridge::bridge]
84
mod ffi {
@@ -41,6 +37,17 @@ mod ffi {
4137
fn val(&self) -> u32;
4238
}
4339

40+
#[swift_bridge(swift_repr = "struct")]
41+
struct ResultTransparentStruct {
42+
pub inner: String,
43+
}
44+
45+
extern "Rust" {
46+
fn rust_func_return_result_null_transparent_struct(
47+
succeed: bool,
48+
) -> Result<(), ResultTransparentStruct>;
49+
}
50+
4451
enum ResultTransparentEnum {
4552
NamedField { data: i32 },
4653
UnnamedFields(u8, String),
@@ -141,6 +148,32 @@ fn rust_func_return_result_unit_struct_opaque_rust(
141148
}
142149
}
143150

151+
fn rust_func_return_result_null_transparent_struct(
152+
succeed: bool,
153+
) -> Result<(), ffi::ResultTransparentStruct> {
154+
if succeed {
155+
Ok(())
156+
} else {
157+
Err(ffi::ResultTransparentStruct {
158+
inner: "failed".to_string(),
159+
})
160+
}
161+
}
162+
163+
impl std::error::Error for ffi::ResultTransparentStruct {}
164+
165+
impl std::fmt::Debug for ffi::ResultTransparentStruct {
166+
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167+
unreachable!("Debug impl was added to pass `Error: Debug + Display` type checking")
168+
}
169+
}
170+
171+
impl std::fmt::Display for ffi::ResultTransparentStruct {
172+
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173+
unreachable!("Display impl was added to pass `Error: Debug + Display` type checking")
174+
}
175+
}
176+
144177
pub struct ResultTestOpaqueRustType {
145178
val: u32,
146179
}

0 commit comments

Comments
 (0)