Work around Foundation NS_TYPED_ENUM bug

Consider code like:

```
// Foo.h
typealias NSString * FooKey NS_EXTENSIBLE_TYPED_ENUM;

// Foo.swift
extension FooKey { … }
```

When Swift binds the extension to `FooKey`, that forces ClangImporter to import `FooKey`. ClangImporter’s newtype logic, among other things, checks whether the underlying type (`Swift.String` here) is Objective-C bridgeable and, if so, makes `FooKey` bridgeable too.

But what happens if this code is actually *in* Foundation, which is where the `extension String: _ObjectiveCBridgeable` lives? Well, if the compiler has already bound that extension, it works fine…but if it hasn’t, `FooKey` ends up unbridgeable, which can cause both type checking failures and IRGen crashes when code tries to use its bridging capabilities. And these symptoms are sensitive to precise details of the order Swift happens to bind extensions in, so e.g. adding empty files to the project can make the bug appear or disappear. Spooky.

Add a narrow hack to ClangImporter (only active for types in Foundation) to *assume* that `String` is bridgeable even if the extension declaring this hasn’t been bound yet.

Fixes rdar://142693093.
This commit is contained in:
Becca Royal-Gordon
2025-01-16 15:42:36 -08:00
parent 992d9af8b7
commit 56b20351e3
4 changed files with 23 additions and 1 deletions

View File

@@ -6335,6 +6335,19 @@ SwiftDeclConverter::importSwiftNewtype(const clang::TypedefNameDecl *decl,
synthesizedProtocols.push_back(kind);
return true;
}
// HACK: This method may be called before all extensions have been bound.
// This is a problem for newtypes in Foundation, which is what provides the
// `String: _ObjectiveCBridgeable` conformance; it can cause us to create
// `String`-backed newtypes which aren't bridgeable, causing typecheck
// failures and crashes down the line (rdar://142693093). Hardcode knowledge
// that this conformance will exist.
// FIXME: Defer adding conformances to newtypes instead of this. (#78731)
if (structDecl->getModuleContext()->isFoundationModule()
&& kind == KnownProtocolKind::ObjectiveCBridgeable
&& computedNominal == ctx.getStringDecl()) {
synthesizedProtocols.push_back(kind);
return true;
}
return false;
};

View File

@@ -18,7 +18,7 @@ func acceptHashable<T: Hashable>(_: T) {}
func acceptComparable<T: Comparable>(_: T) {}
// expected-note@-1 {{where 'T' = 'NSNotification.Name'}}
func testNewTypeWrapper(x: NSNotification.Name, y: NSNotification.Name) {
func testNewTypeWrapper(x: NSNotification.Name, y: NSNotification.Name, z: NSFileAttributeKey) {
acceptEquatable(x)
acceptHashable(x)
acceptComparable(x) // expected-error {{global function 'acceptComparable' requires that 'NSNotification.Name' conform to 'Comparable'}}
@@ -28,6 +28,8 @@ func testNewTypeWrapper(x: NSNotification.Name, y: NSNotification.Name) {
_ = x != y
_ = x.hashValue
_ = x as NSString
_ = z as NSString
}

View File

@@ -4,6 +4,11 @@
public let NSUTF8StringEncoding: UInt = 8
// This extension will cause ClangImporter/newtype_conformance.swift to fail
// unless rdar://142693093 is fixed. To reproduce, it's important that this
// extension come *before* the _ObjectiveCBridgeable extension for String.
extension NSFileAttributeKey { }
extension AnyHashable : _ObjectiveCBridgeable {
public func _bridgeToObjectiveC() -> NSObject {
return NSObject()

View File

@@ -867,6 +867,8 @@ extern void CGColorRelease(CGColorRef color) __attribute__((availability(macosx,
typedef NSString *_Nonnull NSNotificationName
__attribute((swift_newtype(struct)));
typedef NSString *_Nonnull NSFileAttributeKey
__attribute((swift_newtype(struct)));
NS_SWIFT_UNAVAILABLE("Use NSXPCConnection instead")
extern NSString * const NSConnectionReplyMode;