Forward-declared Swift types are not understood by subsequent dependent targets

Hi folks, I've got a question around Swift/Objective-C interoperability works across modules.

I'm trying to refactor some code in a Swift Package target. The target (we'll call this one ATarget) is Objective-C, and I want the new code to be in Swift. So I tried moving this code to the other target (we'll call this one BTarget) and rewrote it in Swift with the appropriate @objc annotations. I also added a dependency from BTarget to ATarget so that ATarget can continue to use these types in their properties and method signatures. I forward declare the types in the appropriate headers in ATarget, and then @import BTarget in the implementation. This all compiles as long as ATarget is the last target in the build to utilize the declarations. However, in subsequent dependents to ATarget, such as test targets or further library targets, if the methods containing these forward-declared types are called, the compiler will fail to understand the entire method signature and throw some cryptic errors about misunderstanding the other parameters in the method.

To put it another way, here's some contrived code:

BTarget/MySwiftProtocol.swift

@objc public protocol MySwiftProtocol {
  // ...
}

ATarget/MyClass.h

@protocol MySwiftProtocol;

@interface MyClass: NSObject
- (instancetype) initWithThing:(id<MySwiftProtocol>)thing
@end

ATarget/MyClass.m

#import "MyClass.h"
@import BTarget;

@implementation MyClass
- (instancetype) initWithThing:(id<MySwiftProtocol>)thing
{
   // ...
}
@end 

I don't know if this is a limitation of interoperability, something I need to configure because I'm in a Swift package, or something completely different. Please let me know if there's anything I can do to fix this, or if I need to clarify anything because my explanation was bad. Thanks!

Hey @sky

I'm also getting this error, are you able to understand and fix the issue ?

Fundamentally, the issue is that the Swift compiler (ClangImporter in particular) intentionally restricts the resolution of forward declarations of Swift types that originate outside the declaration's Clang module. In this example, the type is declared in BTarget, forward-declared in ATarget, and if a subsequent Swift module imports ATarget. Regardless of whether or not BTarget is also imported, ClangImporter will refuse to fully-form the definition of the type in BTarget due to the potential dependency cycles. To be clear, this issue only affects forward declarations of types defined outside the current module, across modular boundaries – import-underlying-module situations are allowed to use forward declarations in this way.

I had a long talk with @allevato a year ago about this and he confirmed it was intentional. The rationale described here is still relevant. The only solution at the current time is to remove the forward declaration and import the appropriate Swift header.

1 Like