I'm exploring C++ -> Swift interop more, and stumbled upon an issue - when my public Swift class uses types from an external module;
import SecondPod
class First {
func createSecond() -> Second {
return Second()
}
}
(Same goes for properties, func parameters, or inheritance/base classes)
..the Swift compiler generates broken code in FirstPod-Swift.h, as it tries to use SecondPod::Second, but that's not defined - I guess it expects me to import SecondPod-Swift.h, which brings it's own problems;
Some C++ types (like specializations of std::function or std::shared_ptr) which are imported in SecondPod's Swift code will be defined twice then via swift::isUsableInGenericContext<...> = true if I include both -Swift.h headers
Importing SecondPod-Swift.h inside FirstPod requires frameworks (static or dynamic), which is a no-go in some setups.
I took a look at a concrete solution in the swift compiler, and inside PrintAsClang we could theoretically just also emit all external type bridges into our own -Swift.h header.
So for the bridged C++ class First inside FirstPod-Swift.h, we could emit Second (inside the SecondPod namespace) as well beforehand, then it would be defined.
Thoughts on that?
We could also introduce an autogenerated *-Swift.cpp file and move the definitions of the generated C++ -> Swift bridges into there and only keep the declarations in the *-Swift.h file (which may be duplicated in your own *-Swift.h files), so that when the vTable/layout changes, the consumer doesn't have to recompile their binaries - you just link against it.
Not sure about value types (enums and structs) though, I don't know if those have specific memory layouts in the -Swift.h header or if everything is a method access anyways.
cc @Xazax-hun@egor.zhdan - is this the right place to post this, or should this go into swift evolution?
I think it's a smaller implementation detail, so I wasn't quite sure if this is the right place to post this.
I think this might pose some usability problems. It is definitely a step forward because some scenarios that did not work before would start to work after but it goes against the goal of having composable headers.
The design goal when organizing headers is that they should always compose, specifically, including header afrom project A and header b from project B should just work.
Emitting the dependencies’ definitions in the header would not satisfy this. In your example, if a project (transitively) depended on both FirstPod and SecondPod, it would not work. So this approach would contribute to a dependency hell.
That being said, our current approach also does not support this sort of composability. But before we give up on this completely, I think we should explore if there is a way to solve this problem without giving up on this. The best case scenario would be if we could generate modular headers such that:
We generate a basic module that contains all the Swift standard library bridging, and some of the basic definitions like the type traits we use (like isUsableInGenericContext).
Generate one modular header per Swift module that:
Imports all the modular headers of the dependency Swift modules
Imports all the C++ headers that this module depends on
Only contains the definitions and trait specializations for the symbols defined in the current Swift module
Admittedly, this is a non-trivial amount of work, and there might be some open questions whether we can make these headers modular at all when the C++ headers we need to consume themselves are not modular. Also, it is not trivial to make sure we do not generate certain headers multiple times, no race conditions during build, and this model fits well into the build process with multiple build systems across all the platforms.
If the two headers were compatible with each other, i.e. if you could #include both of them in a single .cpp file, would it be workable to include both of them whenever you need a declaration from FirstPod?
Thanks!
I'm currently including the external -Swift.h header before including my own -Swift.h header, but this requires frameworks (use_frameworks! in CocoaPods), which unfortunately is kind of a blocker for me.
I'll investigate how/if I can include an external -Swift.h header without frameworks...