Skip to content

Commit c1e8ce6

Browse files
committed
SE-0139: Bridge Cocoa framework structs to NSValue.
For every struct type for which the frameworks provides an NSValue category for boxing and unboxing values of that type, provide an _ObjectiveCBridgeable conformance in the Swift overlay that bridges that struct to NSValue, allowing the structs to be used naturally with id-as-Any APIs and Cocoa container classes. This is mostly a matter of gyb-ing out boilerplate using `NSValue.init(bytes:objCType:)` to construct the instance, `NSValue.objCType` to check its type when casting, and `NSValue.getValue(_:)` to extract the unboxed value, though there are a number of special snowflake cases that need special accommodation: - To maintain proper layering, CoreGraphics structs need to be bridged in the Foundation overlay. - AVFoundation provides the NSValue boxing categories for structs owned by CoreMedia, but it does so using its own internal subclasses of NSValue, and these subclasses do not interop properly with the standard `NSValue` subclasses instantiated by Foundation. To do the right thing, we therefore have to let AVFoundation provide the bridging implementation for the CoreMedia types, and we have to use its category methods to do so. - SceneKit provides NSValue categories to box and unbox SCNVector3, SCNVector4, and SCNMatrix4; however, the methods it provides do so in an unusual way. SCNVector3 and SCNVector4 are packaged into `CGRect`s and then the CGRect is boxed using `valueWithCGRect:`. SCNMatrix4 is copied into a CATransform3D, which is then boxed using `valueWithCATransform3D:` from CoreAnimation. To be consistent with what SceneKit does, use its category methods for these types as well, and when casting, check the type against the type encoding SceneKit uses rather than the type encoding of the expected type.
1 parent 272163f commit c1e8ce6

36 files changed

+584
-111
lines changed

include/swift/AST/ASTContext.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,9 @@ class ASTContext {
514514
ProtocolDecl *getProtocol(KnownProtocolKind kind) const;
515515

516516
/// Determine whether the given nominal type is one of the standard
517-
/// library types that is known a priori to be bridged to a
518-
/// Foundation.
519-
bool isStandardLibraryTypeBridgedInFoundation(NominalTypeDecl *nominal) const;
517+
/// library or Cocoa framework types that is known to be bridged by another
518+
/// module's overlay, for layering or implementation detail reasons.
519+
bool isTypeBridgedInExternalModule(NominalTypeDecl *nominal) const;
520520

521521
/// Get the Objective-C type that a Swift type bridges to, if any.
522522
///

include/swift/AST/KnownIdentifiers.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ IDENTIFIER(Any)
2929
IDENTIFIER(atIndexedSubscript)
3030
IDENTIFIER_(bridgeToObjectiveC)
3131
IDENTIFIER_WITH_NAME(code_, "_code")
32+
IDENTIFIER(CoreGraphics)
33+
IDENTIFIER(CoreMedia)
3234
IDENTIFIER(CGFloat)
3335
IDENTIFIER(CVarArg)
3436
IDENTIFIER(Darwin)

lib/AST/ASTContext.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3982,7 +3982,7 @@ ASTContext::getForeignRepresentationInfo(NominalTypeDecl *nominal,
39823982
}
39833983
}
39843984

3985-
bool ASTContext::isStandardLibraryTypeBridgedInFoundation(
3985+
bool ASTContext::isTypeBridgedInExternalModule(
39863986
NominalTypeDecl *nominal) const {
39873987
return (nominal == getBoolDecl() ||
39883988
nominal == getIntDecl() ||
@@ -3995,8 +3995,18 @@ bool ASTContext::isStandardLibraryTypeBridgedInFoundation(
39953995
nominal == getStringDecl() ||
39963996
nominal == getErrorDecl() ||
39973997
nominal == getAnyHashableDecl() ||
3998-
// Weird one-off case where CGFloat is bridged to NSNumber.
3999-
nominal->getName() == Id_CGFloat);
3998+
// Foundation's overlay depends on the CoreGraphics overlay, but
3999+
// CoreGraphics value types bridge to Foundation objects such as
4000+
// NSValue and NSNumber, so to avoid circular dependencies, the
4001+
// bridging implementations of CG types appear in the Foundation
4002+
// module.
4003+
nominal->getParentModule()->getName() == Id_CoreGraphics ||
4004+
// CoreMedia is a dependency of AVFoundation, but the bridged
4005+
// NSValue implementations for CMTime, CMTimeRange, and
4006+
// CMTimeMapping are provided by AVFoundation, and AVFoundation
4007+
// gets upset if you don't use the NSValue subclasses its factory
4008+
// methods instantiate.
4009+
nominal->getParentModule()->getName() == Id_CoreMedia);
40004010
}
40014011

40024012
Optional<Type>
@@ -4018,7 +4028,7 @@ ASTContext::getBridgedToObjC(const DeclContext *dc, Type type,
40184028
// optimizer will be guaranteed to see the conformance if it exists.
40194029
bool knownBridgedToObjC = false;
40204030
if (auto ntd = type->getAnyNominal())
4021-
knownBridgedToObjC = isStandardLibraryTypeBridgedInFoundation(ntd);
4031+
knownBridgedToObjC = isTypeBridgedInExternalModule(ntd);
40224032

40234033
// TODO: Under id-as-any, container bridging is unconstrained. This check can
40244034
// go away.

lib/SIL/DynamicCasts.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ mustBridgeToSwiftValueBox(Module *M, CanType T) {
5555
if (T->isAnyExistentialType())
5656
return false;
5757

58-
// getBridgedToObjC() might return a null-type for bridged foundation types
59-
// during compiling the standard library. Exclude this case here.
58+
// getBridgedToObjC() might return a null-type for some types
59+
// whose bridging implementation is allowed to live elsewhere. Exclude this
60+
// case here.
6061
if (auto N = T->getAnyNominal())
61-
if (M->getASTContext().isStandardLibraryTypeBridgedInFoundation(N))
62+
if (M->getASTContext().isTypeBridgedInExternalModule(N))
6263
return false;
6364

6465
auto bridgeTy = M->getASTContext().getBridgedToObjC(M, T, nullptr);

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3904,7 +3904,7 @@ void ConformanceChecker::checkConformance() {
39043904
// between an imported Objective-C module and its overlay.
39053905
if (Proto->isSpecificProtocol(KnownProtocolKind::ObjectiveCBridgeable)) {
39063906
if (auto nominal = Adoptee->getAnyNominal()) {
3907-
if (!TC.Context.isStandardLibraryTypeBridgedInFoundation(nominal)) {
3907+
if (!TC.Context.isTypeBridgedInExternalModule(nominal)) {
39083908
auto nominalModule = nominal->getParentModule();
39093909
auto conformanceModule = DC->getParentModule();
39103910
if (nominalModule->getName() != conformanceModule->getName()) {

stdlib/private/StdlibUnittestFoundationExtras/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ add_swift_library(swiftStdlibUnittestFoundationExtras ${SWIFT_STDLIB_LIBRARY_BUI
44
StdlibUnittestFoundationExtras.swift
55
UnavailableFoundationMethodThunks.mm
66

7-
SWIFT_MODULE_DEPENDS Foundation
7+
SWIFT_MODULE_DEPENDS Foundation StdlibUnittest
88
INSTALL_IN_COMPONENT stdlib-experimental)
99

stdlib/private/StdlibUnittestFoundationExtras/StdlibUnittestFoundationExtras.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import ObjectiveC
1414
import Foundation
15+
import StdlibUnittest
1516

1617
internal var _temporaryLocaleCurrentLocale: NSLocale? = nil
1718

@@ -111,3 +112,20 @@ extension NSDictionary {
111112
andKeys: keys)
112113
}
113114
}
115+
116+
public func expectBridgeToNSValue<T>(_ value: T,
117+
nsValueInitializer: ((T) -> NSValue)? = nil,
118+
nsValueGetter: ((NSValue) -> T)? = nil,
119+
equal: (T, T) -> Bool) {
120+
let object = value as AnyObject
121+
let nsValue = object as! NSValue
122+
if let nsValueInitializer = nsValueInitializer {
123+
expectEqual(nsValueInitializer(value), nsValue)
124+
}
125+
if let nsValueGetter = nsValueGetter {
126+
expectTrue(equal(value, nsValueGetter(nsValue)))
127+
}
128+
expectTrue(equal(value, object as! T))
129+
130+
}
131+

stdlib/public/SDK/AVFoundation/AVFoundation.swift renamed to stdlib/public/SDK/AVFoundation/AVFoundation.swift.gyb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
@_exported import AVFoundation // Clang module
2+
import CoreMedia
23
import Foundation
34

5+
%{
6+
from gyb_foundation_support import \
7+
ObjectiveCBridgeableImplementationForNSValueWithCategoryMethods
8+
}%
49

510
extension AVError {
611
/// The device name.
@@ -39,3 +44,23 @@ extension AVError {
3944
}
4045
}
4146

47+
// Bridge CoreMedia structs to NSValue.
48+
// AVFoundation provides internal NSValue subclasses for these structures that
49+
// are incompatible with the NSConcreteValue subclasses you get using
50+
// -[NSValue valueWithBytes:objCType:].
51+
52+
${ ObjectiveCBridgeableImplementationForNSValueWithCategoryMethods(
53+
Type="CMTime",
54+
initializer="{ NSValue(time: $0) }",
55+
getter="{ $0.timeValue }",
56+
) }
57+
${ ObjectiveCBridgeableImplementationForNSValueWithCategoryMethods(
58+
Type="CMTimeRange",
59+
initializer="{ NSValue(timeRange: $0) }",
60+
getter="{ $0.timeRangeValue }",
61+
) }
62+
${ ObjectiveCBridgeableImplementationForNSValueWithCategoryMethods(
63+
Type="CMTimeMapping",
64+
initializer="{ NSValue(timeMapping: $0) }",
65+
getter="{ $0.timeMappingValue }",
66+
) }

stdlib/public/SDK/AVFoundation/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
add_swift_library(swiftAVFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SDK_OVERLAY
2-
AVFoundation.swift
2+
AVFoundation.swift.gyb
33
TARGET_SDKS OSX IOS IOS_SIMULATOR TVOS TVOS_SIMULATOR
44
SWIFT_MODULE_DEPENDS Foundation CoreMedia
55
SWIFT_MODULE_DEPENDS_OSX AppKit

stdlib/public/SDK/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ add_subdirectory(GLKit)
2929
add_subdirectory(HomeKit)
3030
add_subdirectory(IOKit)
3131
add_subdirectory(Intents)
32+
add_subdirectory(MapKit)
3233
add_subdirectory(ObjectiveC)
3334
add_subdirectory(OpenCL)
3435
add_subdirectory(os)
3536
add_subdirectory(Photos)
37+
add_subdirectory(QuartzCore)
3638
add_subdirectory(SafariServices)
3739
add_subdirectory(SceneKit)
3840
add_subdirectory(simd)

0 commit comments

Comments
 (0)