swift icon indicating copy to clipboard operation
swift copied to clipboard

The autogenerated `-Swift.h` header generates broken code when a class in a module inherits from a class inside another module

Open mrousavy opened this issue 6 months ago • 7 comments

Description

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?

Reproduction

public class First { … }
import FirstPod
public class Second: First { … }

…then try calling Second from within a C++ file in SecondPod

Expected behavior

I expect there to be some sort of forward-declaration mechanism, or a void* boxing mechanism to ensure foreign types can be handled.

Environment

swift-driver version: 1.120.5 Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5) Target: arm64-apple-macosx15.0

Additional information

No response

mrousavy avatar Jul 28 '25 15:07 mrousavy

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.

Out of curiosity, did you try this one? I.e., generating a header that includes both of the generated headers in the right order and using that from C++.

Xazax-hun avatar Jul 30 '25 16:07 Xazax-hun

I did try this in a CocoaPods Xcode project, and the -Swift.h header from one module is just not available in another module. And if I manually add it (via it's fully qualified path in ~/DerivedData/…), I'm getting duplicate symbols build errors.

mrousavy avatar Jul 30 '25 16:07 mrousavy

Hey @Xazax-hun - is there anything you need from my end to help troubleshoot this? I can try to generate a test case for this behaviour to see if this can be reproduced here

mrousavy avatar Jan 14 '26 16:01 mrousavy

Hey, unfortunately, I did not have time to get to this yet :/ I think I should be able to create a reproducer for this one but I have some other things on my plate so it is possible I would have time to get back to this in the near future.

Xazax-hun avatar Jan 14 '26 16:01 Xazax-hun

Thanks & no stress!

I'll try to play around with frameworks. I think the main problem is that I cannot import SecondPod's generated SecondPod-Swift.h header in my FirstPod (before I import FirstPod-Swift.h) - because it needs that type from that header to be defined.

mrousavy avatar Jan 14 '26 16:01 mrousavy

JFYI, even after I managed to import SecondPod-Swift.h before FirstPod-Swift.h, it fails to build because the Swift compiler defines swift::isUsableInGenericContext for the same type twice:

Image

Which makes sense, because both pods use the same C++ type in their Swift code, and vice versa. So that kinda sucks - we need some kind of mechanism to rely on this being defined outside of our own module...

Here's the branch in my repository where I worked on this: https://github.com/mrousavy/nitro/pull/1154

mrousavy avatar Jan 14 '26 19:01 mrousavy

I spent ~2 more days on this to try and work around the issue.

I think one way to solve this is if the generated -Swift.h header doesn't to everything inline, but instead ships a -Swift.cpp file as well which actually contains the definitions. That way we can keep it implementation defined, without leaking undefined external symbols into user code.

Image

Here, the solution could be to move HybridTestObjectSwiftKotlinSpec_cxx::bounceExternalHybrid to a .cpp file, and inside that .cpp file we can import the NitroTestExternal-Swift.h header (the external module that defines HybridSomeExternalObjectSpec_cxx.

Or, if we want to only keep a -Swift.h header (no .cpp file), I guess either define the Swift type from the external module again above the definition, or import it (I think defining again aligns with Swift's current approach, where stuff like swift::String is also kinda redefined for each module)

mrousavy avatar Jan 16 '26 13:01 mrousavy

After adjusting the test examples a bit, I noticed that it doesn't have anything to do with inheritance, it also applies to just any externally used Swift type. Not just base classes. So even this:

import SecondPod

class First {
  func getSecond() -> Second {
    return Second()
  }
}

..fails to compile because the FirstPod-Swift.h header tries to use SecondPod::Second without it being defined.

I think the best approach is to re-define Second in FirstPod-Swift.h, because there's a lot that can go wrong when including SecondPod-Swift.h in FirstPod-Swift.h.

mrousavy avatar Jan 19 '26 10:01 mrousavy