diff --git a/Foundation/Bridging.swift b/Foundation/Bridging.swift index ad0114ba2a..3d5820b2ab 100644 --- a/Foundation/Bridging.swift +++ b/Foundation/Bridging.swift @@ -12,10 +12,24 @@ import CoreFoundation +#if canImport(ObjectiveC) +import ObjectiveC +#endif + public protocol _StructBridgeable { func _bridgeToAny() -> Any } +fileprivate protocol Unwrappable { + func unwrap() -> Any? +} + +extension Optional: Unwrappable { + func unwrap() -> Any? { + return self + } +} + /// - Note: This does not exist currently on Darwin but it is the inverse correlation to the bridge types such that a /// reference type can be converted via a callout to a conversion method. public protocol _StructTypeBridgeable : _StructBridgeable { @@ -54,18 +68,92 @@ internal protocol _NSBridgeable { } +#if !canImport(ObjectiveC) +// The _NSSwiftValue protocol is in the stdlib, and only available on platforms without ObjC. +extension _SwiftValue: _NSSwiftValue {} +#endif + /// - Note: This is an internal boxing value for containing abstract structures -internal final class _SwiftValue : NSObject, NSCopying, _NSSwiftValue { +internal final class _SwiftValue : NSObject, NSCopying { public private(set) var value: Any static func fetch(_ object: AnyObject?) -> Any? { if let obj = object { - return fetch(nonOptional: obj) + let value = fetch(nonOptional: obj) + if let wrapper = value as? Unwrappable, wrapper.unwrap() == nil { + return nil + } else { + return value + } } return nil } + #if canImport(ObjectiveC) + private static var _objCNSNullClassStorage: Any.Type? + private static var objCNSNullClass: Any.Type? { + if let type = _objCNSNullClassStorage { + return type + } + + let name = "NSNull" + let maybeType = name.withCString { cString in + return objc_getClass(cString) + } + + if let type = maybeType as? Any.Type { + _objCNSNullClassStorage = type + return type + } else { + return nil + } + } + + private static var _swiftStdlibSwiftValueClassStorage: Any.Type? + private static var swiftStdlibSwiftValueClass: Any.Type? { + if let type = _swiftStdlibSwiftValueClassStorage { + return type + } + + let name = "_SwiftValue" + let maybeType = name.withCString { cString in + return objc_getClass(cString) + } + + if let type = maybeType as? Any.Type { + _swiftStdlibSwiftValueClassStorage = type + return type + } else { + return nil + } + } + + #endif + static func fetch(nonOptional object: AnyObject) -> Any { + #if canImport(ObjectiveC) + // You can pass the result of a `as AnyObject` expression to this method. This can have one of three results on Darwin: + // - It's a SwiftFoundation type. Bridging will take care of it below. + // - It's nil. The compiler is hardcoded to return [NSNull null] for nils. + // - It's some other Swift type. The compiler will box it in a native _SwiftValue. + // Case 1 is handled below. + // Case 2 is handled here: + if type(of: object as Any) == objCNSNullClass { + return Optional.none as Any + } + // Case 3 is handled here: + if type(of: object as Any) == swiftStdlibSwiftValueClass { + return object + // Since this returns Any, the object is casted almost immediately — e.g.: + // _SwiftValue.fetch(x) as SomeStruct + // which will immediately unbox the native box. For callers, it will be exactly + // as if we returned the unboxed value directly. + } + + // On Linux, case 2 is handled by the stdlib bridging machinery, and case 3 can't happen — + // the compiler will produce SwiftFoundation._SwiftValue boxes rather than ObjC ones. + #endif + if object === kCFBooleanTrue { return true } else if object === kCFBooleanFalse { @@ -79,6 +167,13 @@ internal final class _SwiftValue : NSObject, NSCopying, _NSSwiftValue { } } + static func store(optional value: Any?) -> NSObject? { + if let val = value { + return store(val) + } + return nil + } + static func store(_ value: Any?) -> NSObject? { if let val = value { return store(val) @@ -89,8 +184,20 @@ internal final class _SwiftValue : NSObject, NSCopying, _NSSwiftValue { static func store(_ value: Any) -> NSObject { if let val = value as? NSObject { return val + } else if let opt = value as? Unwrappable, opt.unwrap() == nil { + return NSNull() } else { - return (value as AnyObject) as! NSObject + #if canImport(ObjectiveC) + // On Darwin, this can be a native (ObjC) _SwiftValue. + let boxed = (value as AnyObject) + if !(boxed is NSObject) { + return _SwiftValue(value) // Do not emit native boxes — wrap them in Swift Foundation boxes instead. + } else { + return boxed as! NSObject + } + #else + return (value as AnyObject) as! NSObject + #endif } } diff --git a/Foundation/NSArray.swift b/Foundation/NSArray.swift index e2d2a1df21..0b218d6b4d 100644 --- a/Foundation/NSArray.swift +++ b/Foundation/NSArray.swift @@ -337,7 +337,7 @@ open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCo } else { let val1 = object(at: idx) let val2 = otherArray[idx] - if !((val1 as AnyObject) as! NSObject).isEqual(val2 as AnyObject) { + if !_SwiftValue.store(val1).isEqual(_SwiftValue.store(val2)) { return false } } diff --git a/Foundation/NSDictionary.swift b/Foundation/NSDictionary.swift index 47cde71786..055b27e428 100644 --- a/Foundation/NSDictionary.swift +++ b/Foundation/NSDictionary.swift @@ -396,9 +396,10 @@ open class NSDictionary : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, return false } } else { - let otherBridgeable = otherDictionary[key as! AnyHashable] - let bridgeable = object(forKey: key)! - if !((otherBridgeable as AnyObject) as! NSObject).isEqual(bridgeable as AnyObject) { + let otherBridgeable = otherDictionary[key as! AnyHashable] + let bridgeable = object(forKey: key)! + let equal = _SwiftValue.store(optional: otherBridgeable)?.isEqual(_SwiftValue.store(bridgeable)) + if equal != true { return false } } diff --git a/Foundation/NSError.swift b/Foundation/NSError.swift index a05bf31159..5a760bfaed 100644 --- a/Foundation/NSError.swift +++ b/Foundation/NSError.swift @@ -1383,6 +1383,8 @@ enum UnknownNSError: Error { case missingError } +#if !canImport(ObjectiveC) + public // COMPILER_INTRINSIC func _convertNSErrorToError(_ error: NSError?) -> Error { return error ?? UnknownNSError.missingError @@ -1410,3 +1412,6 @@ func _convertErrorToNSError(_ error: Error) -> NSError { return NSError(domain: domain, code: code, userInfo: userInfo) } } + +#endif + diff --git a/Foundation/NSKeyedArchiver.swift b/Foundation/NSKeyedArchiver.swift index 751199aa5c..be56163a15 100644 --- a/Foundation/NSKeyedArchiver.swift +++ b/Foundation/NSKeyedArchiver.swift @@ -597,7 +597,7 @@ open class NSKeyedArchiver : NSCoder { object = _replacementObject(objv) // bridge value types - object = object as AnyObject + object = _SwiftValue.store(object) objectRef = _referenceObject(object, conditional: conditional) guard let unwrappedObjectRef = objectRef else { diff --git a/Foundation/NSKeyedUnarchiver.swift b/Foundation/NSKeyedUnarchiver.swift index d7f44adfc3..fd1c2eedaa 100644 --- a/Foundation/NSKeyedUnarchiver.swift +++ b/Foundation/NSKeyedUnarchiver.swift @@ -483,7 +483,7 @@ open class NSKeyedUnarchiver : NSCoder { _cacheObject(object!, forReference: objectRef as! _NSKeyedArchiverUID) } } else { - object = dereferencedObject as AnyObject + object = _SwiftValue.store(dereferencedObject) } return _replacementObject(object) diff --git a/Foundation/NSSet.swift b/Foundation/NSSet.swift index 3cb2461c1c..d26b6a6e24 100644 --- a/Foundation/NSSet.swift +++ b/Foundation/NSSet.swift @@ -46,7 +46,7 @@ open class NSSet : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSCodi super.init() let buffer = UnsafeBufferPointer(start: objects, count: cnt) for obj in buffer { - _storage.insert(obj as! NSObject) + _storage.insert(_SwiftValue.store(obj)) } }