When one module references another module, the build won't work

I have two Swift modules/pods: FirstPod and SecondPod. Both use C++ interop, and both call Swift classes/functions from C++.

FirstPod has this class:

open class First { … }

SecondPod has this class:

import FirstPod
public class Second: First { … }

Because Second inherits from First, the SecondPod-Swift.h header file produces build errors since it cannot find Second's base class (First) as that is nowhere included:

- Unknown class name 'First'

Code:

class SWIFT_SYMBOL("s:13Second06FirstC") Second : public FirstPod::First {
public:
  using First::First;
  using First::operator=;
  static SWIFT_INLINE_THUNK Second init() SWIFT_SYMBOL("s:13Second06FirstCACycfc");
protected:
  SWIFT_INLINE_THUNK Second(void * _Nonnull ptr) noexcept : First(ptr) {}
private:
  friend class _impl::_impl_Second;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++17-extensions"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-identifier"
  typedef char $s13Second06FirstCD;
  static inline constexpr $s13Second06FirstCD __swift_mangled_name = 0;
#pragma clang diagnostic pop
#pragma clang diagnostic pop
};
  • Forward-declaring doesn't help either, since it needs a concrete type for a base class.
  • Including FirstPod-Swift.h before I include SecondPod-Swift.h doesn't work either because (as far as I know) you cannot include the -Swift.h header of another module inside your module.

I'm out of ideas at this point. Any suggestions?

While I think the compiler probably should handle this for you, can't you forward declare First? You might might have to copy the declaration from the FirstPod-Swift.h to get the mangling right, but it should be legal. The only reason you can't do this for C++ standard library types is because it's explicitly called out as UB. It's valid to forward declare any non-std declaration.

C++20 modules change this a little, but as long as the declaration is extern "C++" it should still be fine. And I think Swift is still using old-school Clang modules anyway.

Sorry I just now saw this; forward declaring First won't work since it's used as a base class, and the full structure has to be known at that point.

I also can't really import FirstPod-Swift.h since the headers aren't really built to be imported outside of their defining modules (there's a whole bunch of compilation errors and duplicate symbols then).

I had two ideas, but both require changes in the swift compiler;

  1. Have Swift comliler redefine First in my SecondPod-Swift.h as if it was a class defined in it's own module. Not sure how well that behaves when passing the type across modules, I guess it has to rely on implicit ABI safety by just making sure it's always using the exact same Swift compiler version..?
  2. Have the Swift compiler generate a -Swift.cpp file too, which contains the actual definitions, and -Swift.h should become much simpler so that it can be imported in other modules - we can strip a lot of stuff from that to keep it safe. I guess we can also make it even simpler by separating that out into a new header like -PublicSwiftSymbols.h, which only does those symbols.

Does SecondPod-Swift.h not have its own inclusion of FirstPod-Swift.h (or some other umbrella header for that module)? That's what I would expect when a generated header has a dependency on a type in a different module that can't be satisfied by a forward declaration.

For Obj-C headers at least, the compiler generates @import directives for any modules it needs. What gets generated for C++ headers?

No, it doesn't. It also doesn't have includes for C++ types it uses, so I have to include them beforehand in my own autogenerated umbrella header.
Here's an example of how one of those umbrella headers I generate looks like: NitroTest-Swift-Cxx-Umbrella.hpp.

The problem here is that if I want to use the external type (e.g. from NitroTestExternal pod), I cannot just include the NitroTestExternal-Swift.h header (or my NitroTestExternal-Swift-Cxx-Umbrella.hpp) beforehand here, because;

  1. That only works if frameworks (static or dynamic) are enabled, without frameworks it doesn't expose that -Swift.h header.
  2. It contains a lot of redefinitions for the same thing, because I use common types in both Pods which use the same C++ type in both libraries, like a specialization of std::function, std::shared_ptr or more - those would all get redefined in swift::isUsableInGenericContext<...> = true in each -Swift.h header so including both is not an option.

I think the best approach honestly is to just avoid including the external -Swift.h header and have Swift just redefine the external types that are used by my own Pod in my own -Swift.h header.