Happy Wednesday everyone! We’d like to propose an extension to the ClangImporter for Swift <> Objective-C Interop.
See a draft PR for importing Automatic Reference Counting (ARC) pointers in C structs in Objective-C mode here: https://github.com/apple/swift/pull/64381/files. Note that the draft does not do type bridging for strong references yet, as that involves more SILgen work and I'd like some feedback before venturing down that route.
Introduction:
Today Swift can import C structs whose members are Swift-importable value types such as int, BOOL, and __unsafe_unretained ARC pointers, which are all trivially construct- and destructable. Building on Akira’s work enabling strong and weak references in LLVM, we should be able to import these types and declarations.
Motivation:
Since 2018, these pointer types have been available to use in Objective-C/Objective-C++ (and before that in Objective-C++) and Swift has, to date, not been extended to import them. This is a necessary part of Swift <> C++ interop, and we should be able to port default and member-wise construction and destruction of reference type members of value types, including ARC pointers in structs in Objective-C language mode. Puyan has already merged some code for constructing these types using C++ interop here.
Proposed Solution:
#import <Foundation/Foundation.h>
typedef struct WithArcPointers {
__unsafe_unretained NSString *usUR;
__strong _Nonnull NSString *sStr;
__weak _Nullable NSString *wStr;
NSInteger count;
} WithArcPointers;
should import to Swift as
struct WithArcPointers {
init(usUR: String!, sStr: String, wStr: String?, count: Int)
var usUR: Unmanaged<NSString>!
var sStr: String
weak var wStr: NSString?
var count: Int
}
The various arc storage types should be preserved, with the expected type bridging and nullability annotations respected where possible. Weak members must be Optional on the Swift side, and __unsafe_unretained as Unmanaged of an unbridged type. These structs should be member-wise constructable, and if they contain no nonnull members, trivially constructable as well with an available init().
Nonnull weak references in structs, while legal in Clang, will not be imported and the compiler will emit an appropriate diagnostic, as Swift requires weak references to be Optional. The C declaration should probably have a diagnostic as well, in line with ObjC classes that appropriately error, but that’s out of scope of this proposal.
Source Compatibility
This change may introduce name conflicts for users that have created accessible declarations with the same name as any newly imported Clang declarations. Thus we suggest keeping the change off by default initially, and possibly enabling by default for Swift 6.0
Alternatives Considered
Because rewriting those C structs types as Objective-C types will involve non-trivial amount of metadata and more code generation (effectively turning each member access into a computed var with a getter and a setter), such a rewrite may be less desirable than importing the types unbridged. There are binary size tradeoffs to consider here as well as ergonomics of using the bridged Swift types.
More codegen for type bridging should happen in the SIL, but I haven’t gotten to that part yet. If there’s a better place to implement that (either in the ClangImporter or IRgen or all three), I’d appreciate the feedback.
Feedback
We are looking for input on if the proposal can be implemented as described, and any possible edge cases we might not be thinking of. In particular, any member of the community that works with Objective-C interop, or any people working with the Objective-C++ Interop would be great!