Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift warning when returning -> Result<*, *> from a Rust method #295

Open
tmolitor-stud-tu opened this issue Oct 14, 2024 · 17 comments
Open
Labels
good first issue Good for newcomers

Comments

@tmolitor-stud-tu
Copy link

When returning a Result from a Rust struct method, the generated swift code will access ptr in a closure and thus implicitly capture self which creates either a compiler warning, or in stricter environments (like ours) a compiler error.

Furthermore the swift compiler complains that the error returned does not conform to Error. Maybe RustString and other builtin types should automatically conform to Error.

@chinedufn
Copy link
Owner

chinedufn commented Oct 14, 2024

Thanks for the report. Please provide the following (can just edit your issue body):

  1. A minimal swift_bridge::bridge module that leads to this warning.
  2. The version of Swift that you are using.
  3. Since you mention the generated Swift code, please also include an example of the problematic generated code.

I'm noticing that none of our tests test returning a Result from a method, so we probably just need to improve our codegen in that scenario:

//! See also: crates/swift-integration-tests/src/result.rs
use super::{CodegenTest, ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode};
use proc_macro2::TokenStream;
use quote::quote;
/// Test code generation for Rust function that accepts and returns a Result<T, E>
/// where T and E are Strings.
mod extern_rust_fn_result_string {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
fn some_function (arg: Result<String, String>);
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function(
arg: swift_bridge::result::ResultPtrAndPtr
) {
super::some_function(
if arg.is_ok {
std::result::Result::Ok(unsafe { Box::from_raw(arg.ok_or_err as *mut swift_bridge::string::RustString).0 })
} else {
std::result::Result::Err(unsafe { Box::from_raw(arg.ok_or_err as *mut swift_bridge::string::RustString).0 })
}
)
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function<GenericIntoRustString: IntoRustString>(_ arg: RustResult<GenericIntoRustString, GenericIntoRustString>) {
__swift_bridge__$some_function({ switch arg { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: { let rustString = ok.intoRustString(); rustString.isOwned = false; return rustString.ptr }()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: { let rustString = err.intoRustString(); rustString.isOwned = false; return rustString.ptr }()) } }())
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim(
r#"
void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg);
"#,
);
#[test]
fn extern_rust_fn_result_string() {
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 accepts a Result<T, E> where T and E are
/// opaque Rust types.
mod extern_rust_fn_arg_result_opaque_rust {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeType;
fn some_function (arg: Result<SomeType, SomeType>);
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function(
arg: swift_bridge::result::ResultPtrAndPtr
) {
super::some_function(
if arg.is_ok {
std::result::Result::Ok(unsafe { *Box::from_raw(arg.ok_or_err as *mut super::SomeType) })
} else {
std::result::Result::Err(unsafe { *Box::from_raw(arg.ok_or_err as *mut super::SomeType) })
}
)
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function(_ arg: RustResult<SomeType, SomeType>) {
__swift_bridge__$some_function({ switch arg { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: {ok.isOwned = false; return ok.ptr;}()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: {err.isOwned = false; return err.ptr;}()) } }())
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg);
"#,
);
#[test]
fn extern_rust_fn_arg_result_opaque_rust() {
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 accepts a Result<T, E> where T and E are
/// opaque Rust types.
mod extern_rust_fn_return_result_opaque_rust {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeType;
fn some_function () -> Result<SomeType, SomeType>;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> swift_bridge::result::ResultPtrAndPtr {
match super::some_function() {
Ok(ok) => {
swift_bridge::result::ResultPtrAndPtr {
is_ok: true,
ok_or_err: Box::into_raw(Box::new({
let val: super::SomeType = ok;
val
})) as *mut super::SomeType as *mut std::ffi::c_void
}
}
Err(err) => {
swift_bridge::result::ResultPtrAndPtr {
is_ok: false,
ok_or_err: Box::into_raw(Box::new({
let val: super::SomeType = err;
val
})) as *mut super::SomeType as *mut std::ffi::c_void
}
}
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> SomeType {
try { let val = __swift_bridge__$some_function(); if val.is_ok { return SomeType(ptr: val.ok_or_err!) } else { throw SomeType(ptr: val.ok_or_err!) } }()
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
struct __private__ResultPtrAndPtr __swift_bridge__$some_function(void);
"#,
);
#[test]
fn extern_rust_fn_return_result_opaque_rust() {
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 accepts and returns a Result<T, E>
/// where T and E are opaque Swift types.
mod extern_rust_fn_result_opaque_swift {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Swift" {
type SomeType;
}
extern "Rust" {
fn some_function (arg: Result<SomeType, SomeType>);
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function(
arg: swift_bridge::result::ResultPtrAndPtr
) {
super::some_function(
if arg.is_ok {
std::result::Result::Ok(unsafe { SomeType(arg.ok_or_err) })
} else {
std::result::Result::Err(unsafe { SomeType(arg.ok_or_err) })
}
)
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function(_ arg: RustResult<SomeType, SomeType>) {
__swift_bridge__$some_function({ switch arg { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: Unmanaged.passRetained(ok).toOpaque()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: Unmanaged.passRetained(err).toOpaque()) } }())
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg);
"#,
);
#[test]
fn extern_rust_fn_result_opaque_swift() {
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 accepts a Result<(), E> where E is an
/// opaque Rust type.
mod extern_rust_fn_return_result_null_and_opaque_rust {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeType;
fn some_function () -> Result<(), SomeType>;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> *mut super::SomeType {
match super::some_function() {
Ok(ok) => std::ptr::null_mut(),
Err(err) => Box::into_raw(Box::new({
let val: super::SomeType = err;
val
})) as *mut super::SomeType
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> () {
try { let val = __swift_bridge__$some_function(); if val != nil { throw SomeType(ptr: val!) } else { return } }()
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
void* __swift_bridge__$some_function(void);
"#,
);
#[test]
fn extern_rust_fn_return_result_opaque_rust() {
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 accepts a Result<T, E> where T is a UnitStruct and E is an
/// opaque Rust type.
mod extern_rust_fn_return_result_unit_and_opaque_rust {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
struct UnitType;
extern "Rust" {
type SomeType;
fn some_function () -> Result<UnitType, SomeType>;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> *mut super::SomeType {
match super::some_function() {
Ok(ok) => std::ptr::null_mut(),
Err(err) => Box::into_raw(Box::new({
let val: super::SomeType = err;
val
})) as *mut super::SomeType
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> UnitType {
try { let val = __swift_bridge__$some_function(); if val != nil { throw SomeType(ptr: val!) } else { return UnitType() } }()
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
void* __swift_bridge__$some_function(void);
"#,
);
#[test]
fn extern_rust_fn_return_result_unit_and_opaque_rust() {
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 Result<T, E> where T is a opaque Rust type and
/// E is a transparent enum type.
mod extern_rust_fn_return_result_opaque_rust_type_and_transparent_enum_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeOkType;
}
enum SomeErrEnum {
Variant1,
Variant2(i32),
}
extern "Rust" {
fn some_function() -> Result<SomeOkType, SomeErrEnum>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultSomeOkTypeAndSomeErrEnum{
#[allow(unused)]
Ok(*mut super::SomeOkType),
#[allow(unused)]
Err(__swift_bridge__SomeErrEnum),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultSomeOkTypeAndSomeErrEnum{
match super::some_function() {
Ok(ok) => ResultSomeOkTypeAndSomeErrEnum::Ok(Box::into_raw(Box::new({
let val: super::SomeOkType = ok;
val
})) as *mut super::SomeOkType),
Err(err) => ResultSomeOkTypeAndSomeErrEnum::Err(err.into_ffi_repr()),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> SomeOkType {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultOk: return SomeOkType(ptr: val.payload.ok) case __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag {__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultOk, __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultErr} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag;
union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields {void* ok; struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum{__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_enum_type_and_opaque_rust_type() {
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 Result<T, E> where T is a transparent enum type and
/// E is a opaque Rust type.
mod extern_rust_fn_return_result_transparent_enum_type_and_opaque_rust_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeOkEnum {
Variant1,
Variant2(i32),
}
extern "Rust" {
type SomeErrType;
}
extern "Rust" {
fn some_function() -> Result<SomeOkEnum, SomeErrType>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultSomeOkEnumAndSomeErrType{
#[allow(unused)]
Ok(__swift_bridge__SomeOkEnum),
#[allow(unused)]
Err(*mut super::SomeErrType),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultSomeOkEnumAndSomeErrType{
match super::some_function() {
Ok(ok) => ResultSomeOkEnumAndSomeErrType::Ok(ok.into_ffi_repr()),
Err(err) => ResultSomeOkEnumAndSomeErrType::Err(Box::into_raw(Box::new({
let val: super::SomeErrType = err;
val
})) as *mut super::SomeErrType),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> SomeOkEnum {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultOk: return val.payload.ok.intoSwiftRepr() case __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultErr: throw SomeErrType(ptr: val.payload.err) default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag {__swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultOk, __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultErr} __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag;
union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields {struct __swift_bridge__$SomeOkEnum ok; void* err;};
typedef struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType{__swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag tag; union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields payload;} __swift_bridge__$ResultSomeOkEnumAndSomeErrType;
"#,
r#"struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_enum_type_and_opaque_rust_type() {
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 Result<T, E> where T is () and
/// E is a transparent enum type.
mod extern_rust_fn_return_result_unit_type_and_transparent_enum_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeErrEnum {
Variant1,
Variant2(i32),
}
extern "Rust" {
fn some_function() -> Result<(), SomeErrEnum>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultVoidAndSomeErrEnum{
#[allow(unused)]
Ok,
#[allow(unused)]
Err(__swift_bridge__SomeErrEnum),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultVoidAndSomeErrEnum{
match super::some_function() {
Ok(ok) => ResultVoidAndSomeErrEnum::Ok,
Err(err) => ResultVoidAndSomeErrEnum::Err(err.into_ffi_repr()),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> () {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultVoidAndSomeErrEnum$ResultOk: return case __swift_bridge__$ResultVoidAndSomeErrEnum$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultVoidAndSomeErrEnum$Tag {__swift_bridge__$ResultVoidAndSomeErrEnum$ResultOk, __swift_bridge__$ResultVoidAndSomeErrEnum$ResultErr} __swift_bridge__$ResultVoidAndSomeErrEnum$Tag;
union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields {struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultVoidAndSomeErrEnum{__swift_bridge__$ResultVoidAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultVoidAndSomeErrEnum __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_enum_type_and_opaque_rust_type() {
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 Result<T, E> where T is a tuple type and
/// E is a transparent enum type.
mod extern_rust_fn_return_result_tuple_type_and_transparent_enum_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeErrEnum {
Variant1,
Variant2,
}
extern "Rust" {
fn some_function() -> Result<(i32, u32), SomeErrEnum>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::ContainsMany(vec![
quote! {
#[repr(C)]
pub enum ResultTupleI32U32AndSomeErrEnum{
#[allow(unused)]
Ok(__swift_bridge__tuple_I32U32),
#[allow(unused)]
Err(__swift_bridge__SomeErrEnum),
}
},
quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultTupleI32U32AndSomeErrEnum{
match super::some_function() {
Ok(ok) => ResultTupleI32U32AndSomeErrEnum::Ok({let val = ok; __swift_bridge__tuple_I32U32(val.0, val.1)}),
Err(err) => ResultTupleI32U32AndSomeErrEnum::Err(err.into_ffi_repr()),
}
}
},
quote! {
#[repr(C)]
#[doc(hidden)]
pub struct __swift_bridge__tuple_I32U32(i32, u32);
},
])
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> (Int32, UInt32) {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultOk: return { let val = val.payload.ok; return (val._0, val._1); }() case __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag {__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultOk, __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultErr} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag;
union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields {struct __swift_bridge__$tuple$I32U32 ok; struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum{__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag tag; union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields payload;} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum __swift_bridge__$some_function(void)"#,
r#"typedef struct __swift_bridge__$tuple$I32U32 { int32_t _0; uint32_t _1; } __swift_bridge__$tuple$I32U32;"#,
])
}
#[test]
fn extern_rust_fn_return_result_tuple_type_and_transparent_enum_type() {
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 Result<T, E> where T is () and
/// E is a transparent struct type.
mod extern_rust_fn_return_result_unit_type_and_transparent_struct_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
struct SomeErrStruct {
inner: String,
}
extern "Rust" {
fn some_function() -> Result<(), SomeErrStruct>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultVoidAndSomeErrStruct{
#[allow(unused)]
Ok,
#[allow(unused)]
Err(__swift_bridge__SomeErrStruct),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultVoidAndSomeErrStruct{
match super::some_function() {
Ok(ok) => ResultVoidAndSomeErrStruct::Ok,
Err(err) => ResultVoidAndSomeErrStruct::Err(err.into_ffi_repr()),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> () {
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() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultVoidAndSomeErrStruct$Tag {__swift_bridge__$ResultVoidAndSomeErrStruct$ResultOk, __swift_bridge__$ResultVoidAndSomeErrStruct$ResultErr} __swift_bridge__$ResultVoidAndSomeErrStruct$Tag;
union __swift_bridge__$ResultVoidAndSomeErrStruct$Fields {struct __swift_bridge__$SomeErrStruct err;};
typedef struct __swift_bridge__$ResultVoidAndSomeErrStruct{__swift_bridge__$ResultVoidAndSomeErrStruct$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrStruct$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrStruct;
"#,
r#"struct __swift_bridge__$ResultVoidAndSomeErrStruct __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_struct_type_and_opaque_rust_type() {
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();
}
}
//! See also: crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs
#[swift_bridge::bridge]
mod ffi {
struct UnitStruct;
extern "Rust" {
fn rust_func_reflect_result_opaque_rust(
arg: Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType>,
) -> Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType>;
fn rust_func_takes_result_string(arg: Result<String, String>);
fn rust_func_takes_result_opaque_swift(
arg: Result<ResultTestOpaqueSwiftType, ResultTestOpaqueSwiftType>,
);
fn rust_func_return_result_null_opaque_rust(
succeed: bool,
) -> Result<(), ResultTestOpaqueRustType>;
fn rust_func_return_result_unit_struct_opaque_rust(
succeed: bool,
) -> Result<UnitStruct, ResultTestOpaqueRustType>;
}
extern "Rust" {
type ResultTestOpaqueRustType;
#[swift_bridge(init)]
fn new(val: u32) -> ResultTestOpaqueRustType;
fn val(&self) -> u32;
}
extern "Swift" {
type ResultTestOpaqueSwiftType;
fn val(&self) -> u32;
}
#[swift_bridge(swift_repr = "struct")]
struct ResultTransparentStruct {
pub inner: String,
}
extern "Rust" {
fn rust_func_return_result_null_transparent_struct(
succeed: bool,
) -> Result<(), ResultTransparentStruct>;
}
enum ResultTransparentEnum {
NamedField { data: i32 },
UnnamedFields(u8, String),
NoFields,
}
extern "Rust" {
fn rust_func_return_result_opaque_rust_transparent_enum(
succeed: bool,
) -> Result<ResultTestOpaqueRustType, ResultTransparentEnum>;
fn rust_func_return_result_transparent_enum_opaque_rust(
succeed: bool,
) -> Result<ResultTransparentEnum, ResultTestOpaqueRustType>;
}
extern "Rust" {
fn rust_func_return_result_unit_type_enum_opaque_rust(
succeed: bool,
) -> Result<(), ResultTransparentEnum>;
}
enum SameEnum {
Variant1,
Variant2,
}
extern "Rust" {
fn same_custom_result_returned_twice_first() -> Result<SameEnum, SameEnum>;
fn same_custom_result_returned_twice_second() -> Result<SameEnum, SameEnum>;
}
extern "Rust" {
fn rust_func_return_result_of_vec_u32() -> Result<Vec<u32>, ResultTestOpaqueRustType>;
fn rust_func_return_result_of_vec_opaque(
) -> Result<Vec<ResultTestOpaqueRustType>, ResultTestOpaqueRustType>;
}
extern "Rust" {
fn rust_func_return_result_tuple_transparent_enum(
succeed: bool,
) -> Result<(i32, ResultTestOpaqueRustType, String), ResultTransparentEnum>;
}
extern "Rust" {
type ThrowingInitializer;
#[swift_bridge(init)]
fn new(succeed: bool) -> Result<ThrowingInitializer, ResultTransparentEnum>;
fn val(&self) -> i32;
}
}
fn rust_func_takes_result_string(arg: Result<String, String>) {
match arg {
Ok(ok) => {
assert_eq!(ok, "Success Message")
}
Err(err) => {
assert_eq!(err, "Error Message")
}
}
}
fn rust_func_reflect_result_opaque_rust(
arg: Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType>,
) -> Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType> {
match arg {
Ok(ok) => {
assert_eq!(ok.val, 111);
Ok(ok)
}
Err(err) => {
assert_eq!(err.val, 222);
Err(err)
}
}
}
fn rust_func_takes_result_opaque_swift(
arg: Result<ffi::ResultTestOpaqueSwiftType, ffi::ResultTestOpaqueSwiftType>,
) {
match arg {
Ok(ok) => {
assert_eq!(ok.val(), 555)
}
Err(err) => {
assert_eq!(err.val(), 666)
}
}
}
fn rust_func_return_result_null_opaque_rust(succeed: bool) -> Result<(), ResultTestOpaqueRustType> {
if succeed {
Ok(())
} else {
Err(ResultTestOpaqueRustType { val: 222 })
}
}
fn rust_func_return_result_unit_struct_opaque_rust(
succeed: bool,
) -> Result<ffi::UnitStruct, ResultTestOpaqueRustType> {
if succeed {
Ok(ffi::UnitStruct)
} else {
Err(ResultTestOpaqueRustType { val: 222 })
}
}
fn rust_func_return_result_null_transparent_struct(
succeed: bool,
) -> Result<(), ffi::ResultTransparentStruct> {
if succeed {
Ok(())
} else {
Err(ffi::ResultTransparentStruct {
inner: "failed".to_string(),
})
}
}
impl std::error::Error for ffi::ResultTransparentStruct {}
impl std::fmt::Debug for ffi::ResultTransparentStruct {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unreachable!("Debug impl was added to pass `Error: Debug + Display` type checking")
}
}
impl std::fmt::Display for ffi::ResultTransparentStruct {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unreachable!("Display impl was added to pass `Error: Debug + Display` type checking")
}
}
pub struct ResultTestOpaqueRustType {
val: u32,
}
impl ResultTestOpaqueRustType {
fn new(val: u32) -> Self {
Self { val }
}
fn val(&self) -> u32 {
self.val
}
}
fn rust_func_return_result_opaque_rust_transparent_enum(
succeed: bool,
) -> Result<ResultTestOpaqueRustType, ffi::ResultTransparentEnum> {
if succeed {
Ok(ResultTestOpaqueRustType::new(123))
} else {
Err(ffi::ResultTransparentEnum::NamedField { data: 123 })
}
}
fn rust_func_return_result_transparent_enum_opaque_rust(
succeed: bool,
) -> Result<ffi::ResultTransparentEnum, ResultTestOpaqueRustType> {
if succeed {
Ok(ffi::ResultTransparentEnum::NamedField { data: 123 })
} else {
Err(ResultTestOpaqueRustType::new(123))
}
}
fn rust_func_return_result_unit_type_enum_opaque_rust(
succeed: bool,
) -> Result<(), ffi::ResultTransparentEnum> {
if succeed {
Ok(())
} else {
Err(ffi::ResultTransparentEnum::NamedField { data: 123 })
}
}
fn same_custom_result_returned_twice_first() -> Result<ffi::SameEnum, ffi::SameEnum> {
todo!()
}
fn same_custom_result_returned_twice_second() -> Result<ffi::SameEnum, ffi::SameEnum> {
todo!()
}
fn rust_func_return_result_of_vec_u32() -> Result<Vec<u32>, ResultTestOpaqueRustType> {
Ok(vec![0, 1, 2])
}
fn rust_func_return_result_of_vec_opaque(
) -> Result<Vec<ResultTestOpaqueRustType>, ResultTestOpaqueRustType> {
Ok(vec![
ResultTestOpaqueRustType::new(0),
ResultTestOpaqueRustType::new(1),
ResultTestOpaqueRustType::new(2),
])
}
fn rust_func_return_result_tuple_transparent_enum(
succeed: bool,
) -> Result<(i32, ResultTestOpaqueRustType, String), ffi::ResultTransparentEnum> {
if succeed {
Ok((123, ResultTestOpaqueRustType::new(123), "hello".to_string()))
} else {
Err(ffi::ResultTransparentEnum::NamedField { data: -123 })
}
}
struct ThrowingInitializer {
val: i32,
}
impl ThrowingInitializer {
fn new(succeed: bool) -> Result<Self, ffi::ResultTransparentEnum> {
if succeed {
Ok(ThrowingInitializer { val: 123 })
} else {
Err(ffi::ResultTransparentEnum::NamedField { data: -123 })
}
}
fn val(&self) -> i32 {
self.val
}
}

@chinedufn
Copy link
Owner

There is an incomplete pull request that implements Error for RustStringRef #282 . Anyone is welcome to take it over.

@tmolitor-stud-tu
Copy link
Author

tmolitor-stud-tu commented Oct 14, 2024

COMMENT EDITED BY chinedufn TO REMOVE MOST OF THE IRRELEVANT CODE

I've used this code:

#[swift_bridge::bridge]
mod ffi {
    //rust struct exported from rust to swift
    extern "Rust" {
        type MonalHtmlParser;

        pub fn select(
            &self,
            selector: String,
            atrribute: Option<String>,
        ) -> Result<Vec<String>, String>;
    }
}

The generated code is the following:

extension MonalHtmlParserRef {
    public func select<GenericIntoRustString: IntoRustString>(_ selector: GenericIntoRustString, _ atrribute: Optional<GenericIntoRustString>) throws -> RustVec<RustString> {
        try { let val = __swift_bridge__$MonalHtmlParser$select(ptr, { let rustString = selector.intoRustString(); rustString.isOwned = false; return rustString.ptr }(), { if let rustString = optionalStringIntoRustString(atrribute) { rustString.isOwned = false; return rustString.ptr } else { return nil } }()); if val.is_ok { return RustVec(ptr: val.ok_or_err!) } else { throw RustString(ptr: val.ok_or_err!) } }()
    }
}

@tmolitor-stud-tu
Copy link
Author

afaik the codegen must just replace the ptr with self.ptr in this part:

try { let val = __swift_bridge__$MonalHtmlParser$select(ptr, { let rustString = selector.intoRustString(); rustString.isOwned = false; return rustString.ptr }(), { if let rustString = optionalStringIntoRustString(atrribute) { rustString.isOwned = false; return rustString.ptr } else { return nil } }()); if val.is_ok { return RustVec(ptr: val.ok_or_err!) } else { throw RustString(ptr: val.ok_or_err!) } }()

-->

try { let val = __swift_bridge__$MonalHtmlParser$select(self.ptr, { let rustString = selector.intoRustString(); rustString.isOwned = false; return rustString.ptr }(), { if let rustString = optionalStringIntoRustString(atrribute) { rustString.isOwned = false; return rustString.ptr } else { return nil } }()); if val.is_ok { return RustVec(ptr: val.ok_or_err!) } else { throw RustString(ptr: val.ok_or_err!) } }()

@chinedufn chinedufn changed the title Compiler warning/error when returning Result<> Swift warning when returning -> Result<*, *> from a Rust method Oct 14, 2024
@chinedufn
Copy link
Owner

Guide

Fail tests on warnings

Make the test-swift-rust-integration.sh fail if the xcodebuild emits any warnings:

xcodebuild \
-project SwiftRustIntegrationTestRunner.xcodeproj \
-scheme SwiftRustIntegrationTestRunner \
clean test

Not sure if xcodebuild has a flag for upgrading warnings to errors.
If it does not, can pipe xcodebuild's stderr to a temp file and then grep the temp file for warnings.

Add integration test

Add an integration test that calls a Rust method that returns a Result:

//! See also: crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs
#[swift_bridge::bridge]
mod ffi {
struct UnitStruct;
extern "Rust" {
fn rust_func_reflect_result_opaque_rust(
arg: Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType>,
) -> Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType>;
fn rust_func_takes_result_string(arg: Result<String, String>);
fn rust_func_takes_result_opaque_swift(
arg: Result<ResultTestOpaqueSwiftType, ResultTestOpaqueSwiftType>,
);
fn rust_func_return_result_null_opaque_rust(
succeed: bool,
) -> Result<(), ResultTestOpaqueRustType>;
fn rust_func_return_result_unit_struct_opaque_rust(
succeed: bool,
) -> Result<UnitStruct, ResultTestOpaqueRustType>;
}
extern "Rust" {
type ResultTestOpaqueRustType;
#[swift_bridge(init)]
fn new(val: u32) -> ResultTestOpaqueRustType;
fn val(&self) -> u32;
}
extern "Swift" {
type ResultTestOpaqueSwiftType;
fn val(&self) -> u32;
}
#[swift_bridge(swift_repr = "struct")]
struct ResultTransparentStruct {
pub inner: String,
}
extern "Rust" {
fn rust_func_return_result_null_transparent_struct(
succeed: bool,
) -> Result<(), ResultTransparentStruct>;
}
enum ResultTransparentEnum {
NamedField { data: i32 },
UnnamedFields(u8, String),
NoFields,
}
extern "Rust" {
fn rust_func_return_result_opaque_rust_transparent_enum(
succeed: bool,
) -> Result<ResultTestOpaqueRustType, ResultTransparentEnum>;
fn rust_func_return_result_transparent_enum_opaque_rust(
succeed: bool,
) -> Result<ResultTransparentEnum, ResultTestOpaqueRustType>;
}
extern "Rust" {
fn rust_func_return_result_unit_type_enum_opaque_rust(
succeed: bool,
) -> Result<(), ResultTransparentEnum>;
}
enum SameEnum {
Variant1,
Variant2,
}
extern "Rust" {
fn same_custom_result_returned_twice_first() -> Result<SameEnum, SameEnum>;
fn same_custom_result_returned_twice_second() -> Result<SameEnum, SameEnum>;
}
extern "Rust" {
fn rust_func_return_result_of_vec_u32() -> Result<Vec<u32>, ResultTestOpaqueRustType>;
fn rust_func_return_result_of_vec_opaque(
) -> Result<Vec<ResultTestOpaqueRustType>, ResultTestOpaqueRustType>;
}
extern "Rust" {
fn rust_func_return_result_tuple_transparent_enum(
succeed: bool,
) -> Result<(i32, ResultTestOpaqueRustType, String), ResultTransparentEnum>;
}
extern "Rust" {
type ThrowingInitializer;
#[swift_bridge(init)]
fn new(succeed: bool) -> Result<ThrowingInitializer, ResultTransparentEnum>;
fn val(&self) -> i32;
}
}
fn rust_func_takes_result_string(arg: Result<String, String>) {
match arg {
Ok(ok) => {
assert_eq!(ok, "Success Message")
}
Err(err) => {
assert_eq!(err, "Error Message")
}
}
}
fn rust_func_reflect_result_opaque_rust(
arg: Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType>,
) -> Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType> {
match arg {
Ok(ok) => {
assert_eq!(ok.val, 111);
Ok(ok)
}
Err(err) => {
assert_eq!(err.val, 222);
Err(err)
}
}
}
fn rust_func_takes_result_opaque_swift(
arg: Result<ffi::ResultTestOpaqueSwiftType, ffi::ResultTestOpaqueSwiftType>,
) {
match arg {
Ok(ok) => {
assert_eq!(ok.val(), 555)
}
Err(err) => {
assert_eq!(err.val(), 666)
}
}
}
fn rust_func_return_result_null_opaque_rust(succeed: bool) -> Result<(), ResultTestOpaqueRustType> {
if succeed {
Ok(())
} else {
Err(ResultTestOpaqueRustType { val: 222 })
}
}
fn rust_func_return_result_unit_struct_opaque_rust(
succeed: bool,
) -> Result<ffi::UnitStruct, ResultTestOpaqueRustType> {
if succeed {
Ok(ffi::UnitStruct)
} else {
Err(ResultTestOpaqueRustType { val: 222 })
}
}
fn rust_func_return_result_null_transparent_struct(
succeed: bool,
) -> Result<(), ffi::ResultTransparentStruct> {
if succeed {
Ok(())
} else {
Err(ffi::ResultTransparentStruct {
inner: "failed".to_string(),
})
}
}
impl std::error::Error for ffi::ResultTransparentStruct {}
impl std::fmt::Debug for ffi::ResultTransparentStruct {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unreachable!("Debug impl was added to pass `Error: Debug + Display` type checking")
}
}
impl std::fmt::Display for ffi::ResultTransparentStruct {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unreachable!("Display impl was added to pass `Error: Debug + Display` type checking")
}
}
pub struct ResultTestOpaqueRustType {
val: u32,
}
impl ResultTestOpaqueRustType {
fn new(val: u32) -> Self {
Self { val }
}
fn val(&self) -> u32 {
self.val
}
}
fn rust_func_return_result_opaque_rust_transparent_enum(
succeed: bool,
) -> Result<ResultTestOpaqueRustType, ffi::ResultTransparentEnum> {
if succeed {
Ok(ResultTestOpaqueRustType::new(123))
} else {
Err(ffi::ResultTransparentEnum::NamedField { data: 123 })
}
}
fn rust_func_return_result_transparent_enum_opaque_rust(
succeed: bool,
) -> Result<ffi::ResultTransparentEnum, ResultTestOpaqueRustType> {
if succeed {
Ok(ffi::ResultTransparentEnum::NamedField { data: 123 })
} else {
Err(ResultTestOpaqueRustType::new(123))
}
}
fn rust_func_return_result_unit_type_enum_opaque_rust(
succeed: bool,
) -> Result<(), ffi::ResultTransparentEnum> {
if succeed {
Ok(())
} else {
Err(ffi::ResultTransparentEnum::NamedField { data: 123 })
}
}
fn same_custom_result_returned_twice_first() -> Result<ffi::SameEnum, ffi::SameEnum> {
todo!()
}
fn same_custom_result_returned_twice_second() -> Result<ffi::SameEnum, ffi::SameEnum> {
todo!()
}
fn rust_func_return_result_of_vec_u32() -> Result<Vec<u32>, ResultTestOpaqueRustType> {
Ok(vec![0, 1, 2])
}
fn rust_func_return_result_of_vec_opaque(
) -> Result<Vec<ResultTestOpaqueRustType>, ResultTestOpaqueRustType> {
Ok(vec![
ResultTestOpaqueRustType::new(0),
ResultTestOpaqueRustType::new(1),
ResultTestOpaqueRustType::new(2),
])
}
fn rust_func_return_result_tuple_transparent_enum(
succeed: bool,
) -> Result<(i32, ResultTestOpaqueRustType, String), ffi::ResultTransparentEnum> {
if succeed {
Ok((123, ResultTestOpaqueRustType::new(123), "hello".to_string()))
} else {
Err(ffi::ResultTransparentEnum::NamedField { data: -123 })
}
}
struct ThrowingInitializer {
val: i32,
}
impl ThrowingInitializer {
fn new(succeed: bool) -> Result<Self, ffi::ResultTransparentEnum> {
if succeed {
Ok(ThrowingInitializer { val: 123 })
} else {
Err(ffi::ResultTransparentEnum::NamedField { data: -123 })
}
}
fn val(&self) -> i32 {
self.val
}
}

//
// ResultTests.swift
// SwiftRustIntegrationTestRunnerTests
//
// Created by Frankie Nwafili on 9/20/22.
import XCTest
@testable import SwiftRustIntegrationTestRunner
class ResultTests: XCTestCase {
/// Verify that we can pass a Result<String, String> from Swift -> Rust
func testSwiftCallRustResultString() throws {
rust_func_takes_result_string(.Ok("Success Message"))
rust_func_takes_result_string(.Err("Error Message"))
}
/// Verify that we can pass a Result<OpaqueRust, OpaqueRust> from Swift -> Rust
func testSwiftCallRustResultOpaqueRust() throws {
let reflectedOk = try! rust_func_reflect_result_opaque_rust(
.Ok(ResultTestOpaqueRustType(111))
)
XCTAssertEqual(reflectedOk.val(), 111)
do {
let _ = try rust_func_reflect_result_opaque_rust(
.Err(ResultTestOpaqueRustType(222))
)
XCTFail("The function should have returned an error.")
} catch let error as ResultTestOpaqueRustType {
XCTAssertEqual(error.val(), 222)
}
}
/// Verify that we can pass a Result<OpaqueSwift, OpaqueSwift> from Swift -> Rust
func testSwiftCallRustResultOpaqueSwift() throws {
rust_func_takes_result_opaque_swift(
.Ok(ResultTestOpaqueSwiftType(val: 555))
)
rust_func_takes_result_opaque_swift(
.Err(ResultTestOpaqueSwiftType(val: 666))
)
}
/// Verify that we can receive a Result<(), OpaqueRust> from Rust
func testSwiftCallRustResultNullOpaqueRust() throws {
try! rust_func_return_result_null_opaque_rust(true)
do {
try rust_func_return_result_null_opaque_rust(false)
XCTFail("The function should have returned an error.")
} catch let error as ResultTestOpaqueRustType {
XCTAssertEqual(error.val(), 222)
}
}
/// Verify that we can receive a Result<UnitStruct, OpaqueRust> from Rust
func testSwiftCallRustResultUnitStructOpaqueRust() throws {
try! rust_func_return_result_unit_struct_opaque_rust(true)
do {
try rust_func_return_result_unit_struct_opaque_rust(false)
XCTFail("The function should have returned an error.")
} catch let error as ResultTestOpaqueRustType {
XCTAssertEqual(error.val(), 222)
}
}
/// Verify that we can receive a Result<OpaqueRust, TransparentEnum> from Rust
func testResultOpaqueRustTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") {
_ in
do {
let _ :ResultTestOpaqueRustType = try rust_func_return_result_opaque_rust_transparent_enum(true)
} catch {
XCTFail()
}
}
XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
let _: ResultTestOpaqueRustType = try rust_func_return_result_opaque_rust_transparent_enum(false)
XCTFail("The function should have returned an error.")
} catch let error as ResultTransparentEnum {
switch error {
case .NamedField(let data):
XCTAssertEqual(data, 123)
case .UnnamedFields(_, _):
XCTFail()
case .NoFields:
XCTFail()
}
} catch {
XCTFail()
}
}
}
/// Verify that we can receive a Result<TransparentEnum, OpaqueRust> from Rust
func testResultTransparentEnumOpaqueRust() throws {
XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") {
_ in
do {
let resultTransparentEnum : ResultTransparentEnum = try rust_func_return_result_transparent_enum_opaque_rust(true)
switch resultTransparentEnum {
case .NamedField(let data):
XCTAssertEqual(data, 123)
case .UnnamedFields(_, _):
XCTFail()
case .NoFields:
XCTFail()
}
} catch {
XCTFail()
}
}
XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
let _: ResultTransparentEnum = try rust_func_return_result_transparent_enum_opaque_rust(false)
XCTFail("The function should have returned an error.")
} catch _ as ResultTestOpaqueRustType {
} catch {
XCTFail()
}
}
}
/// Verify that we can receive a Result<(), TransparentEnum> from Rust
func testResultUnitTypeTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a Unit type") {
_ in
do {
let _ :() = try rust_func_return_result_unit_type_enum_opaque_rust(true)
} catch {
XCTFail()
}
}
XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
let _ :() = try rust_func_return_result_unit_type_enum_opaque_rust(false)
XCTFail("The function should have returned an error.")
} catch let error as ResultTransparentEnum {
switch error {
case .NamedField(let data):
XCTAssertEqual(data, 123)
case .UnnamedFields(_, _):
XCTFail()
case .NoFields:
XCTFail()
}
} catch {
XCTFail()
}
}
}
/// Verify that we can receive a Result<(primitive type, OpaqueRustType, String), TransparentEnum> from Rust
func testResultTupleTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a tuple type") {
_ in
do {
let tuple: (Int32, ResultTestOpaqueRustType, RustString) = try rust_func_return_result_tuple_transparent_enum(true)
XCTAssertEqual(tuple.0, 123)
XCTAssertEqual(tuple.1.val(), ResultTestOpaqueRustType(123).val())
XCTAssertEqual(tuple.2.toString(), "hello")
} catch {
XCTFail()
}
}
XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
let _: (Int32, ResultTestOpaqueRustType, RustString) = try rust_func_return_result_tuple_transparent_enum(false)
XCTFail("The function should have returned an error.")
} catch let error as ResultTransparentEnum {
switch error {
case .NamedField(let data):
XCTAssertEqual(data, -123)
case .UnnamedFields(_, _):
XCTFail()
case .NoFields:
XCTFail()
}
} catch {
XCTFail()
}
}
}
/// Verify that we can receive a Result<(), TransparentStruct> from Rust
func testResultNullTransparentStruct() throws {
try! rust_func_return_result_null_transparent_struct(true)
do {
try rust_func_return_result_null_transparent_struct(false)
XCTFail("The function should have returned an error.")
} catch let error as ResultTransparentStruct {
XCTAssertEqual(error.inner.toString(), "failed")
}
XCTContext.runActivity(named: "Should return a Unit type") {
_ in
do {
let _ :() = try rust_func_return_result_unit_type_enum_opaque_rust(true)
} catch {
XCTFail()
}
}
XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
let _ :() = try rust_func_return_result_unit_type_enum_opaque_rust(false)
XCTFail("The function should have returned an error.")
} catch let error as ResultTransparentEnum {
switch error {
case .NamedField(let data):
XCTAssertEqual(data, 123)
case .UnnamedFields(_, _):
XCTFail()
case .NoFields:
XCTFail()
}
} catch {
XCTFail()
}
}
}
/// Verify that we can receive a Result<Vec<>, OpaqueRust> from Rust
func testSwiftCallRustResultVecUInt32Rust() throws {
let vec = try! rust_func_return_result_of_vec_u32()
XCTAssertEqual(vec.len(), 3)
for (i, value) in vec.enumerated() {
XCTAssertEqual(UInt32(i), value)
}
}
func testSwiftCallRustResultVecOpaqueRust() throws {
let vec = try! rust_func_return_result_of_vec_opaque()
XCTAssertEqual(vec.len(), 3)
for (i, value) in vec.enumerated() {
XCTAssertEqual(UInt32(i), value.val())
}
}
/// Verify that we can use throwing initializers defined on the Rust side.
func testThrowingInitializers() throws {
XCTContext.runActivity(named: "Should fail") {
_ in
do {
let throwingInitializer = try ThrowingInitializer(false)
} catch let error as ResultTransparentEnum {
if case .NamedField(data: -123) = error {
// This case should pass.
} else {
XCTFail()
}
} catch {
XCTFail()
}
}
XCTContext.runActivity(named: "Should succeed") {
_ in
let throwingInitializer = try! ThrowingInitializer(true)
XCTAssertEqual(throwingInitializer.val(), 123)
}
}
}

Add codegen test

Add a codegen test where we return a Result from a Rust method:

//! See also: crates/swift-integration-tests/src/result.rs
use super::{CodegenTest, ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode};
use proc_macro2::TokenStream;
use quote::quote;
/// Test code generation for Rust function that accepts and returns a Result<T, E>
/// where T and E are Strings.
mod extern_rust_fn_result_string {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
fn some_function (arg: Result<String, String>);
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function(
arg: swift_bridge::result::ResultPtrAndPtr
) {
super::some_function(
if arg.is_ok {
std::result::Result::Ok(unsafe { Box::from_raw(arg.ok_or_err as *mut swift_bridge::string::RustString).0 })
} else {
std::result::Result::Err(unsafe { Box::from_raw(arg.ok_or_err as *mut swift_bridge::string::RustString).0 })
}
)
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function<GenericIntoRustString: IntoRustString>(_ arg: RustResult<GenericIntoRustString, GenericIntoRustString>) {
__swift_bridge__$some_function({ switch arg { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: { let rustString = ok.intoRustString(); rustString.isOwned = false; return rustString.ptr }()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: { let rustString = err.intoRustString(); rustString.isOwned = false; return rustString.ptr }()) } }())
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim(
r#"
void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg);
"#,
);
#[test]
fn extern_rust_fn_result_string() {
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 accepts a Result<T, E> where T and E are
/// opaque Rust types.
mod extern_rust_fn_arg_result_opaque_rust {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeType;
fn some_function (arg: Result<SomeType, SomeType>);
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function(
arg: swift_bridge::result::ResultPtrAndPtr
) {
super::some_function(
if arg.is_ok {
std::result::Result::Ok(unsafe { *Box::from_raw(arg.ok_or_err as *mut super::SomeType) })
} else {
std::result::Result::Err(unsafe { *Box::from_raw(arg.ok_or_err as *mut super::SomeType) })
}
)
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function(_ arg: RustResult<SomeType, SomeType>) {
__swift_bridge__$some_function({ switch arg { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: {ok.isOwned = false; return ok.ptr;}()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: {err.isOwned = false; return err.ptr;}()) } }())
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg);
"#,
);
#[test]
fn extern_rust_fn_arg_result_opaque_rust() {
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 accepts a Result<T, E> where T and E are
/// opaque Rust types.
mod extern_rust_fn_return_result_opaque_rust {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeType;
fn some_function () -> Result<SomeType, SomeType>;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> swift_bridge::result::ResultPtrAndPtr {
match super::some_function() {
Ok(ok) => {
swift_bridge::result::ResultPtrAndPtr {
is_ok: true,
ok_or_err: Box::into_raw(Box::new({
let val: super::SomeType = ok;
val
})) as *mut super::SomeType as *mut std::ffi::c_void
}
}
Err(err) => {
swift_bridge::result::ResultPtrAndPtr {
is_ok: false,
ok_or_err: Box::into_raw(Box::new({
let val: super::SomeType = err;
val
})) as *mut super::SomeType as *mut std::ffi::c_void
}
}
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> SomeType {
try { let val = __swift_bridge__$some_function(); if val.is_ok { return SomeType(ptr: val.ok_or_err!) } else { throw SomeType(ptr: val.ok_or_err!) } }()
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
struct __private__ResultPtrAndPtr __swift_bridge__$some_function(void);
"#,
);
#[test]
fn extern_rust_fn_return_result_opaque_rust() {
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 accepts and returns a Result<T, E>
/// where T and E are opaque Swift types.
mod extern_rust_fn_result_opaque_swift {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Swift" {
type SomeType;
}
extern "Rust" {
fn some_function (arg: Result<SomeType, SomeType>);
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function(
arg: swift_bridge::result::ResultPtrAndPtr
) {
super::some_function(
if arg.is_ok {
std::result::Result::Ok(unsafe { SomeType(arg.ok_or_err) })
} else {
std::result::Result::Err(unsafe { SomeType(arg.ok_or_err) })
}
)
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function(_ arg: RustResult<SomeType, SomeType>) {
__swift_bridge__$some_function({ switch arg { case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: Unmanaged.passRetained(ok).toOpaque()) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: Unmanaged.passRetained(err).toOpaque()) } }())
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg);
"#,
);
#[test]
fn extern_rust_fn_result_opaque_swift() {
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 accepts a Result<(), E> where E is an
/// opaque Rust type.
mod extern_rust_fn_return_result_null_and_opaque_rust {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeType;
fn some_function () -> Result<(), SomeType>;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> *mut super::SomeType {
match super::some_function() {
Ok(ok) => std::ptr::null_mut(),
Err(err) => Box::into_raw(Box::new({
let val: super::SomeType = err;
val
})) as *mut super::SomeType
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> () {
try { let val = __swift_bridge__$some_function(); if val != nil { throw SomeType(ptr: val!) } else { return } }()
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
void* __swift_bridge__$some_function(void);
"#,
);
#[test]
fn extern_rust_fn_return_result_opaque_rust() {
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 accepts a Result<T, E> where T is a UnitStruct and E is an
/// opaque Rust type.
mod extern_rust_fn_return_result_unit_and_opaque_rust {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
struct UnitType;
extern "Rust" {
type SomeType;
fn some_function () -> Result<UnitType, SomeType>;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> *mut super::SomeType {
match super::some_function() {
Ok(ok) => std::ptr::null_mut(),
Err(err) => Box::into_raw(Box::new({
let val: super::SomeType = err;
val
})) as *mut super::SomeType
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> UnitType {
try { let val = __swift_bridge__$some_function(); if val != nil { throw SomeType(ptr: val!) } else { return UnitType() } }()
}
"#,
)
}
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
r#"
void* __swift_bridge__$some_function(void);
"#,
);
#[test]
fn extern_rust_fn_return_result_unit_and_opaque_rust() {
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 Result<T, E> where T is a opaque Rust type and
/// E is a transparent enum type.
mod extern_rust_fn_return_result_opaque_rust_type_and_transparent_enum_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeOkType;
}
enum SomeErrEnum {
Variant1,
Variant2(i32),
}
extern "Rust" {
fn some_function() -> Result<SomeOkType, SomeErrEnum>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultSomeOkTypeAndSomeErrEnum{
#[allow(unused)]
Ok(*mut super::SomeOkType),
#[allow(unused)]
Err(__swift_bridge__SomeErrEnum),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultSomeOkTypeAndSomeErrEnum{
match super::some_function() {
Ok(ok) => ResultSomeOkTypeAndSomeErrEnum::Ok(Box::into_raw(Box::new({
let val: super::SomeOkType = ok;
val
})) as *mut super::SomeOkType),
Err(err) => ResultSomeOkTypeAndSomeErrEnum::Err(err.into_ffi_repr()),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> SomeOkType {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultOk: return SomeOkType(ptr: val.payload.ok) case __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag {__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultOk, __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultErr} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag;
union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields {void* ok; struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum{__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_enum_type_and_opaque_rust_type() {
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 Result<T, E> where T is a transparent enum type and
/// E is a opaque Rust type.
mod extern_rust_fn_return_result_transparent_enum_type_and_opaque_rust_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeOkEnum {
Variant1,
Variant2(i32),
}
extern "Rust" {
type SomeErrType;
}
extern "Rust" {
fn some_function() -> Result<SomeOkEnum, SomeErrType>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultSomeOkEnumAndSomeErrType{
#[allow(unused)]
Ok(__swift_bridge__SomeOkEnum),
#[allow(unused)]
Err(*mut super::SomeErrType),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultSomeOkEnumAndSomeErrType{
match super::some_function() {
Ok(ok) => ResultSomeOkEnumAndSomeErrType::Ok(ok.into_ffi_repr()),
Err(err) => ResultSomeOkEnumAndSomeErrType::Err(Box::into_raw(Box::new({
let val: super::SomeErrType = err;
val
})) as *mut super::SomeErrType),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> SomeOkEnum {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultOk: return val.payload.ok.intoSwiftRepr() case __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultErr: throw SomeErrType(ptr: val.payload.err) default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag {__swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultOk, __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultErr} __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag;
union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields {struct __swift_bridge__$SomeOkEnum ok; void* err;};
typedef struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType{__swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag tag; union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields payload;} __swift_bridge__$ResultSomeOkEnumAndSomeErrType;
"#,
r#"struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_enum_type_and_opaque_rust_type() {
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 Result<T, E> where T is () and
/// E is a transparent enum type.
mod extern_rust_fn_return_result_unit_type_and_transparent_enum_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeErrEnum {
Variant1,
Variant2(i32),
}
extern "Rust" {
fn some_function() -> Result<(), SomeErrEnum>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultVoidAndSomeErrEnum{
#[allow(unused)]
Ok,
#[allow(unused)]
Err(__swift_bridge__SomeErrEnum),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultVoidAndSomeErrEnum{
match super::some_function() {
Ok(ok) => ResultVoidAndSomeErrEnum::Ok,
Err(err) => ResultVoidAndSomeErrEnum::Err(err.into_ffi_repr()),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> () {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultVoidAndSomeErrEnum$ResultOk: return case __swift_bridge__$ResultVoidAndSomeErrEnum$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultVoidAndSomeErrEnum$Tag {__swift_bridge__$ResultVoidAndSomeErrEnum$ResultOk, __swift_bridge__$ResultVoidAndSomeErrEnum$ResultErr} __swift_bridge__$ResultVoidAndSomeErrEnum$Tag;
union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields {struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultVoidAndSomeErrEnum{__swift_bridge__$ResultVoidAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultVoidAndSomeErrEnum __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_enum_type_and_opaque_rust_type() {
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 Result<T, E> where T is a tuple type and
/// E is a transparent enum type.
mod extern_rust_fn_return_result_tuple_type_and_transparent_enum_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeErrEnum {
Variant1,
Variant2,
}
extern "Rust" {
fn some_function() -> Result<(i32, u32), SomeErrEnum>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::ContainsMany(vec![
quote! {
#[repr(C)]
pub enum ResultTupleI32U32AndSomeErrEnum{
#[allow(unused)]
Ok(__swift_bridge__tuple_I32U32),
#[allow(unused)]
Err(__swift_bridge__SomeErrEnum),
}
},
quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultTupleI32U32AndSomeErrEnum{
match super::some_function() {
Ok(ok) => ResultTupleI32U32AndSomeErrEnum::Ok({let val = ok; __swift_bridge__tuple_I32U32(val.0, val.1)}),
Err(err) => ResultTupleI32U32AndSomeErrEnum::Err(err.into_ffi_repr()),
}
}
},
quote! {
#[repr(C)]
#[doc(hidden)]
pub struct __swift_bridge__tuple_I32U32(i32, u32);
},
])
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> (Int32, UInt32) {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultOk: return { let val = val.payload.ok; return (val._0, val._1); }() case __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultErr: throw val.payload.err.intoSwiftRepr() default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag {__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultOk, __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultErr} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag;
union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields {struct __swift_bridge__$tuple$I32U32 ok; struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum{__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag tag; union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields payload;} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum __swift_bridge__$some_function(void)"#,
r#"typedef struct __swift_bridge__$tuple$I32U32 { int32_t _0; uint32_t _1; } __swift_bridge__$tuple$I32U32;"#,
])
}
#[test]
fn extern_rust_fn_return_result_tuple_type_and_transparent_enum_type() {
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 Result<T, E> where T is () and
/// E is a transparent struct type.
mod extern_rust_fn_return_result_unit_type_and_transparent_struct_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
struct SomeErrStruct {
inner: String,
}
extern "Rust" {
fn some_function() -> Result<(), SomeErrStruct>;
}
}
}
}
// In Rust 1.79.0 dead_code warnings are issued for wrapped data in enums in spite of the enum
// having `#[repr(C)]`. `#[allow(unused)]` can be removed following resolution and release of this
// issue: https://github.com/rust-lang/rust/issues/126706
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultVoidAndSomeErrStruct{
#[allow(unused)]
Ok,
#[allow(unused)]
Err(__swift_bridge__SomeErrStruct),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultVoidAndSomeErrStruct{
match super::some_function() {
Ok(ok) => ResultVoidAndSomeErrStruct::Ok,
Err(err) => ResultVoidAndSomeErrStruct::Err(err.into_ffi_repr()),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> () {
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() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultVoidAndSomeErrStruct$Tag {__swift_bridge__$ResultVoidAndSomeErrStruct$ResultOk, __swift_bridge__$ResultVoidAndSomeErrStruct$ResultErr} __swift_bridge__$ResultVoidAndSomeErrStruct$Tag;
union __swift_bridge__$ResultVoidAndSomeErrStruct$Fields {struct __swift_bridge__$SomeErrStruct err;};
typedef struct __swift_bridge__$ResultVoidAndSomeErrStruct{__swift_bridge__$ResultVoidAndSomeErrStruct$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrStruct$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrStruct;
"#,
r#"struct __swift_bridge__$ResultVoidAndSomeErrStruct __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_struct_type_and_opaque_rust_type() {
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();
}
}

@chinedufn chinedufn added the good first issue Good for newcomers label Oct 14, 2024
@chinedufn
Copy link
Owner

@tmolitor-stud-tu can you update your issue body with the warning that you are seeing?

This way anyone that works on this issue knows exactly what to look for.

@tmolitor-stud-tu
Copy link
Author

If you tell me where the code resides that generates the snippet with the missing self I quickly do it myself. I'm new to your codebase and I don't want (nor do I have the time) to work through everything just to find the right place to add the missing "self".

@brittlewis12
Copy link
Contributor

I'm running into the same errors myself — and they're errors, not warnings. My package.swift targets swift 5.9 because I need to target visionOS and iOS 17 minimums.

❯ swift build
Building for debugging...
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:34:212: error: thrown expression type 'RustString' does not conform to 'Error'
        try { let val = __swift_bridge__$Handle$load(ptr, { let rustString = path.intoRustString(); rustString.isOwned = false; return rustString.ptr }(), params.intoFfiRepr()); if val != nil { throw RustString(ptr: val!) } else { return } }()
                                                                                                                                                                                                                   ^~~~~~~~~~~~~~~~~~~~~
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:38:122: error: thrown expression type 'RustString' does not conform to 'Error'
        try { let val = __swift_bridge__$Handle$set_params(ptr, params.intoFfiRepr()); if val != nil { throw RustString(ptr: val!) } else { return } }()
                                                                                                                         ^~~~~~~~~~~~~~~~~~~~~
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:42:281: error: thrown expression type 'RustString' does not conform to 'Error'
        try { let val = __swift_bridge__$Handle$execute(ptr, { let rustString = input.intoRustString(); rustString.isOwned = false; return rustString.ptr }(), UnsafeMutableRawPointer(mutating: callback)); if val.is_ok { return RustString(ptr: val.ok_or_err!) } else { throw RustString(ptr: val.ok_or_err!) } }()
                                                                                                                                                                                                                                                                                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:46:96: error: thrown expression type 'RustString' does not conform to 'Error'
        try { let val = __swift_bridge__$Handle$reset(ptr); if val != nil { throw RustString(ptr: val!) } else { return } }()
                                                                                               ^~~~~~~~~~~~~~~~~~~~~
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:50:195: error: thrown expression type 'RustString' does not conform to 'Error'
        try { let val = __swift_bridge__$Handle$reload(ptr, { let rustString = input.intoRustString(); rustString.isOwned = false; return rustString.ptr }()); if val != nil { throw RustString(ptr: val!) } else { return } }()
                                                                                                                                                                                                  ^~~~~~~~~~~~~~~~~~~~~
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:34:59: error: implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
        try { let val = __swift_bridge__$Handle$load(ptr, { let rustString = path.intoRustString(); rustString.isOwned = false; return rustString.ptr }(), params.intoFfiRepr()); if val != nil { throw RustString(ptr: val!) } else { return } }()
                                                          ^
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:38:72: error: implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
        try { let val = __swift_bridge__$Handle$set_params(ptr, params.intoFfiRepr()); if val != nil { throw RustString(ptr: val!) } else { return } }()
                                                                       ^
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:42:62: error: implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
        try { let val = __swift_bridge__$Handle$execute(ptr, { let rustString = input.intoRustString(); rustString.isOwned = false; return rustString.ptr }(), UnsafeMutableRawPointer(mutating: callback)); if val.is_ok { return RustString(ptr: val.ok_or_err!) } else { throw RustString(ptr: val.ok_or_err!) } }()
                                                             ^
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:46:68: error: implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
        try { let val = __swift_bridge__$Handle$reset(ptr); if val != nil { throw RustString(ptr: val!) } else { return } }()
                                                                   ^
~/code/project_ffi/ProjectFFI/Sources/ProjectFFI/project_ffi.swift:50:67: error: implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
        try { let val = __swift_bridge__$Handle$reload(ptr, { let rustString = input.intoRustString(); rustString.isOwned = false; return rustString.ptr }()); if val != nil { throw RustString(ptr: val!) } else { return } }()
                                                                  ^
error: fatalError

@chinedufn
Copy link
Owner

You can remove the ok: bool here https://github.com/brittlewis12/swift-bridge/blob/627e7db5dc660ea7706a6c4494edb97850b0da0f/crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs#L76

Other than that that branch looks great. Thanks.

@brittlewis12
Copy link
Contributor

done! #296

@brittlewis12
Copy link
Contributor

how can we address the implicit self errors? i'd like to be able to reproduce them with your testing harness(es)

@chinedufn
Copy link
Owner

I wrote a guide on addressing the implicit self errors here -> #295 (comment)

If you have any questions please feel free to ask. Happy to answer.

chinedufn pushed a commit that referenced this issue Oct 14, 2024
Support Rust returning `-> Result<_, String>`, e.g.:

```rust
#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn do_fallible_work() -> Result<(), String>;
    }
}
```

---

Incorporate the changes from #282 to implement swift's `Error` protocol for ruststring, & extend them with the tests requested in #295 (comment)
@brittlewis12
Copy link
Contributor

brittlewis12 commented Oct 14, 2024

I wrote a guide on addressing the implicit self errors here -> #295 (comment)

If you have any questions please feel free to ask. Happy to answer.

@chinedufn Thanks!

re: upgrading warnings to errors — it seems that may not be fine-grained enough:

As you suspected, there was no apparent xcodebuild flag, but I was able to set a build configuration just for the test target’s Swift compiler via Xcode. However, that has produced other, unrelated errors when running the integration tests script:

# ./test-swift-rust-integration

Testing failed:
	Declaration of 'struct __swift_bridge__$ResultAsyncResultOkEnumAndAsyncResultErrEnum' will not be visible outside of this function
	Declaration of 'struct __swift_bridge__$ResultAsyncResultOpaqueRustType1AndAsyncResultErrEnum' will not be visible outside of this function
	Declaration of 'struct __swift_bridge__$ResultAsyncResultOkEnumAndAsyncResultOpaqueRustType1' will not be visible outside of this function
	Declaration of 'struct __swift_bridge__$ResultVoidAndAsyncResultErrEnum' will not be visible outside of this function
	Failed to import bridging header '/Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/Headers/BridgingHeader.h'
	Command SwiftCompile failed with a nonzero exit code
	Testing cancelled because the build failed.

** TEST FAILED **


The following build commands failed:
	SwiftEmitModule normal arm64 Emitting\ module\ for\ SwiftRustIntegrationTestRunnerTests (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SingleRepresentationTypeElisionTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/RustFnUsesOpaqueSwiftTypeTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/PrimitiveTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 Compiling\ OpaqueSwiftStructTests.swift,\ SwiftFnUsesOpaqueRustTypeTests.swift /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OpaqueSwiftStructTests.swift /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SwiftFnUsesOpaqueRustTypeTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OpaqueSwiftStructTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SwiftFnUsesOpaqueRustTypeTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/CallbackTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedEnumAttributeTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
	SwiftCompile normal arm64 Compiling\ OptionTests.swift,\ SharedStructAttributeTests.swift,\ StringTests.swift /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/OptionTests.swift /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/SharedStructAttributeTests.swift /Users/tito/code/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/StringTests.swift (in target 'SwiftRustIntegrationTestRunnerTests' from project 'SwiftRustIntegrationTestRunner')
(10 failures)

Aside from upgrading all warnings to errors, do you have any other ideas for reproducing this?

@brittlewis12
Copy link
Contributor

brittlewis12 commented Oct 14, 2024

Reading thru the latest CI run logs, it would seem there are some material warnings being surfaced during tests — tho none of these match the error I am seeing: error: implicit use of 'self' in closure; use 'self.' to make capture semantics explicit:

/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/ASwiftStack.swift:22:9: warning: initialization of 'UnsafePointer<UInt8>' results in a dangling pointer
        UnsafePointer(self.stack)
        ^~~~~~~~~~~~~~~~~~~~~~~~~
/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/ASwiftStack.swift:22:28: note: implicit argument conversion from '[UInt8]' to 'UnsafePointer<UInt8>' produces a pointer valid only for the duration of the call to 'init(_:)'
        UnsafePointer(self.stack)
                      ~~~~~^~~~~
/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunner/ASwiftStack.swift:22:28: note: use the 'withUnsafeBufferPointer' method on Array in order to explicitly convert argument to buffer pointer valid for a defined scope
        UnsafePointer(self.stack)

&

/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/PointerTests.swift:23:23: warning: initialization of 'UnsafeRawPointer' results in a dangling pointer
        let pointer = UnsafeRawPointer(value)
                      ^~~~~~~~~~~~~~~~~~~~~~~
/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/PointerTests.swift:23:40: note: implicit argument conversion from '[Int]' to 'UnsafeRawPointer' produces a pointer valid only for the duration of the call to 'init(_:)'
        let pointer = UnsafeRawPointer(value)
                                       ^~~~~
/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/PointerTests.swift:23:40: note: use the 'withUnsafeBytes' method on Array in order to explicitly convert argument to buffer pointer valid for a defined scope
        let pointer = UnsafeRawPointer(value)
                                       ^
/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/PointerTests.swift:24:27: warning: initialization of 'UnsafeMutableRawPointer' results in a dangling pointer
        let pointer_mut = UnsafeMutableRawPointer(mutating: value)
                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/PointerTests.swift:24:61: note: implicit argument conversion from '[Int]' to 'UnsafeRawPointer' produces a pointer valid only for the duration of the call to 'init(mutating:)'
        let pointer_mut = UnsafeMutableRawPointer(mutating: value)
                                                            ^~~~~
/Users/runner/work/swift-bridge/swift-bridge/SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/PointerTests.swift:24:61: note: use the 'withUnsafeBytes' method on Array in order to explicitly convert argument to buffer pointer valid for a defined scope
        let pointer_mut = UnsafeMutableRawPointer(mutating: value)

@brittlewis12
Copy link
Contributor

When I apply these changes locally, my project compiles successfully and I can use my rust <> swift bridge without further modfication!

brittlewis12@6d26ade

@chinedufn
Copy link
Owner

chinedufn commented Oct 14, 2024

Thanks for investigating this and finding the basis of a solution.

Aside from upgrading all warnings to errors, do you have any other ideas for reproducing this?

  1. PR the new build configuration change but leave it commented out with a TODO to enable it once we fix the errors. Can link to your comment Swift warning when returning -> Result<*, *> from a Rust method #295 (comment)

  2. Write the xcodebuild stderr to a temp file. xcodebuild ... args ... 2>&1 tee /tmp/output.txt. That command should make the output go to both the console and the file.

  3. Grep the /tmp/output.txt for the warning. grep -q WARNING_PATTERN output.txt && echo "warning found" && exit 1

  4. Leave a TODO to remove this grepping hack once we enable build errors in the Xcode project

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

3 participants