Should NSObject conform to CVarArg in swift-corelibs-foundation?

I recently ran into a Foundation compatibility difference on non-Darwin platforms: NSObject itself does not conform to CVarArg in swift-corelibs-foundation, so NSObject subclasses that do not have their own explicit conformance cannot be passed to %@ formatting APIs.

For example, this works on Darwin Foundation:

import Foundation

let value = NSAttributedString(string: "hello")
let result = NSString(format: "%@", value)
print(result)

but currently fails to compile on Linux with:

error: argument type 'NSAttributedString' does not conform to expected type 'CVarArg'

There was a previous related discussion and implementation for NSString / String:

That fixed NSString and String, but the broader NSObject subclass case still appears to be missing.

I opened an issue and PR here:

The proposed fix is to make NSObject conform to CVarArg, so subclasses such as NSAttributedString can be passed as %@ arguments consistently on non-Darwin Foundation. This also makes the existing explicit NSString: CVarArg conformance redundant, since NSString inherits from NSObject.

The implementation follows the same object vararg encoding approach:

extension NSObject: CVarArg {
    @inlinable // c-abi
    public var _cVarArgEncoding: [Int] {
#if _runtime(_ObjC) // Note: I removed this in the final PR
        _autorelease(self)
#endif
        return _encodeBitsAsWords(self)
    }
}

A few questions I would like feedback on:

  • Does putting the conformance on NSObject match the intended Foundation model for non-Darwin platforms?
  • Are there any ABI, source compatibility, or behavioral concerns with moving this from NSString to NSObject?

Thanks!

Update after looking more closely at the CoreFoundation formatting path: I no longer think NSObject: CVarArg is the right fix for swift-corelibs-foundation.

The important distinction is that CVarArg only gets the object pointer into the C varargs list. For %@, CoreFoundation still needs to interpret that pointer. In the current path, if there is no Foundation-provided description callback, formatting calls __CFCopyFormattingDescription(...) and then falls back to CFCopyDescription(...). Those functions expect a valid CF object/runtime layout.

That means this is not safe for arbitrary Swift NSObject subclasses. A plain Swift subclass of NSObject does not necessarily have _CFInfo(typeID:) or a layout that can be interpreted by the CF runtime.

NSAttributedString, however, is different in swift-corelibs-foundation. It is intentionally CF-compatible: it has _CFInfo(typeID: CFAttributedStringGetTypeID()), stores the fields expected by CFAttributedString, and has an internal _cfObject bitcast to CFAttributedString. So NSString(format: "%@", attributedString) can be supported by going through the existing CFAttributedString description path.

Because of that, I narrowed the PR from making NSObject conform to CVarArg to only making NSAttributedString conform.

Update: Make NSObject conform to CVarArg by Kyle-Ye · Pull Request #5490 · swiftlang/swift-corelibs-foundation · GitHub is merged.

The 6.4 cherry-pick is waiting for a review: (6.4.x cherry-pick) Make NSObject conform to CVarArg by Kyle-Ye · Pull Request #5492 · swiftlang/swift-corelibs-foundation · GitHub

1 Like