chinedufn / swift-bridge

swift-bridge facilitates Rust and Swift interop.
https://chinedufn.github.io/swift-bridge
Apache License 2.0
842 stars 62 forks source link

#[swift_bridge(already_declared)] causes duplicate header definitions #207

Open amsam0 opened 1 year ago

amsam0 commented 1 year ago

Here's an example. In lib.rs I have:

#[swift_bridge::bridge]
mod ffi {
    #[derive(Debug)]
    #[swift_bridge(swift_name = "MinimuxerError")]
    enum Errors {
        ...
    }

    extern "Rust" {
        ...
    }
}

In another module, I have:

#[swift_bridge::bridge]
mod ffi {
    #[swift_bridge(already_declared, swift_name = "MinimuxerError")]
    enum Errors {}

    extern "Rust" {
        fn cool_function() -> Result<(), Errors>;
    }
}

In another module, I have:

#[swift_bridge::bridge]
mod ffi {
    #[swift_bridge(already_declared, swift_name = "MinimuxerError")]
    enum Errors {}

    extern "Rust" {
        fn another_cool_function() -> Result<(), Errors>;
    }
}

This causes duplicate header definitions (ResultVoidAndErrors$Tag):

// File automatically generated by swift-bridge.
#include <stdint.h>
struct __swift_bridge__$ResultVoidAndErrors __swift_bridge__$another_cool_function();
typedef enum __swift_bridge__$ResultVoidAndErrors$Tag {__swift_bridge__$ResultVoidAndErrors$ResultOk, __swift_bridge__$ResultVoidAndErrors$ResultErr} __swift_bridge__$ResultVoidAndErrors$Tag;
union __swift_bridge__$ResultVoidAndErrors$Fields {struct __swift_bridge__$Errors err;};
typedef struct __swift_bridge__$ResultVoidAndErrors{__swift_bridge__$ResultVoidAndErrors$Tag tag; union __swift_bridge__$ResultVoidAndErrors$Fields payload;} __swift_bridge__$ResultVoidAndErrors;

// File automatically generated by swift-bridge.
#include <stdint.h>
struct __swift_bridge__$ResultVoidAndErrors __swift_bridge__$cool_function();
typedef enum __swift_bridge__$ResultVoidAndErrors$Tag {__swift_bridge__$ResultVoidAndErrors$ResultOk, __swift_bridge__$ResultVoidAndErrors$ResultErr} __swift_bridge__$ResultVoidAndErrors$Tag;
union __swift_bridge__$ResultVoidAndErrors$Fields {struct __swift_bridge__$MinimuxerError err;};
typedef struct __swift_bridge__$ResultVoidAndErrors{__swift_bridge__$ResultVoidAndErrors$Tag tag; union __swift_bridge__$ResultVoidAndErrors$Fields payload;} __swift_bridge__$ResultVoidAndErrors;

// File automatically generated by swift-bridge.
#include <stdbool.h>
typedef enum __swift_bridge__$MinimuxerErrorTag { ... } __swift_bridge__$MinimuxerErrorTag;
typedef struct __swift_bridge__$MinimuxerError { __swift_bridge__$MinimuxerErrorTag tag; } __swift_bridge__$MinimuxerError;
typedef struct __swift_bridge__$Option$MinimuxerError { bool is_some; __swift_bridge__$MinimuxerError val; } __swift_bridge__$Option$MinimuxerError;
void* __swift_bridge__$MinimuxerError$Debug(__swift_bridge__$MinimuxerError this);
void* __swift_bridge__$Vec_MinimuxerError$new(void);
void __swift_bridge__$Vec_MinimuxerError$drop(void* vec_ptr);
void __swift_bridge__$Vec_MinimuxerError$push(void* vec_ptr, __swift_bridge__$MinimuxerError item);
__swift_bridge__$Option$MinimuxerError __swift_bridge__$Vec_MinimuxerError$pop(void* vec_ptr);
__swift_bridge__$Option$MinimuxerError __swift_bridge__$Vec_MinimuxerError$get(void* vec_ptr, uintptr_t index);
__swift_bridge__$Option$MinimuxerError __swift_bridge__$Vec_MinimuxerError$get_mut(void* vec_ptr, uintptr_t index);
uintptr_t __swift_bridge__$Vec_MinimuxerError$len(void* vec_ptr);
void* __swift_bridge__$Vec_MinimuxerError$as_ptr(void* vec_ptr);

which Xcode then complains about.

A workaround is to add this to your build script:

// out_dir is probably `./generated/{crate name}/`
for path in std::fs::read_dir(out_dir).unwrap() {
    let path = path
        .unwrap()
        .path()
        .file_name()
        .unwrap()
        .to_str()
        .unwrap()
        .to_string();
    let out_path = format!("{out_dir}{path}");

    // remove duplicate lines from minimuxer.h (see https://github.com/chinedufn/swift-bridge/issues/207)
    if path.ends_with(&format!("{}.h", env!("CARGO_PKG_NAME"))) {
        let input = std::fs::read_to_string(&out_path)
            .unwrap()
            // sometimes swift-bridge won't add a newline after defintions
            .replace(";typedef", ";\ntypedef");
        let mut output: Vec<String> = vec![];
        for line in input.split("\n") {
            let line = line.to_owned();
            if !output.contains(&line) || line.len() <= 0 || line.starts_with("//") {
                output.push(line);
            }
        }
        std::fs::write(&out_path, output.join("\n")).unwrap();
    }
}
chinedufn commented 1 year ago

Ah, thanks for reporting.

We generate C types on the fly to support bridging arbitrary Result<T, E> combinations.

We do this once per bridge module so that we can use the same Result<T, E> multiple times per bridge module.

What's happening here is that since our Result<T, E> support type codegen doesn't currently normalize across multiple bridge modules, the Result support type in your example is getting generated twice.

Right now swift-bridge-build iterates over all passed in Rust files and generated code for the bridge modules one by one.

https://github.com/chinedufn/swift-bridge/blob/23d60feaa0f8e92f2c854d8665868aa5573ef505/crates/swift-bridge-build/src/lib.rs#L23-L42

One solution would be to make swift-bridge-build first parse all of the bridge modules, then combine them into some sort of combined data structure, and then generate code from that combined data structure.

Not sure how much of an architectural change that would be. Need to look into it. Seems like you have a hacky workaround in the meantime fortunately.