-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Remove stdlib and runtime dependencies on Foundation and CF #26630
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -417,13 +417,27 @@ internal struct _ContiguousArrayBuffer<Element>: _ArrayBufferProtocol { | |
} | ||
|
||
#if _runtime(_ObjC) | ||
|
||
/// Convert to an NSArray. | ||
/// | ||
/// - Precondition: `Element` is bridged to Objective-C. | ||
/// | ||
/// - Complexity: O(1). | ||
@inlinable | ||
@usableFromInline | ||
Catfish-Man marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same, isn't this an ABI change? |
||
internal __consuming func _asCocoaArray() -> AnyObject { | ||
// _asCocoaArray was @inlinable in Swift 5.0 and 5.1, which means that there | ||
// are existing apps out there that effectively have the old implementation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: missing "." |
||
// Be careful with future changes to this function. Here be dragons! | ||
// The old implementation was | ||
// if count == 0 { | ||
// return _emptyArrayStorage | ||
// } | ||
// if _isBridgedVerbatimToObjectiveC(Element.self) { | ||
// return _storage | ||
// } | ||
// return __SwiftDeferredNSArray(_nativeStorage: _storage) | ||
|
||
_connectOrphanedFoundationSubclassesIfNeeded() | ||
if count == 0 { | ||
return _emptyArrayStorage | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,10 +47,12 @@ internal func getChild<T>(of value: T, type: Any.Type, index: Int) -> (label: St | |
internal func _getQuickLookObject<T>(_: T) -> AnyObject? | ||
|
||
@_silgen_name("_swift_stdlib_NSObject_isKindOfClass") | ||
internal func _isImpl(_ object: AnyObject, kindOf: AnyObject) -> Bool | ||
internal func _isImpl(_ object: AnyObject, kindOf: UnsafePointer<CChar>) -> Bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jrose-apple I'm assuming There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are not, but it is sufficient to prevent dead code stripping (finally). Since this is only called from the runtime, which is in the same binary, this should be okay. |
||
|
||
internal func _is(_ object: AnyObject, kindOf `class`: String) -> Bool { | ||
return _isImpl(object, kindOf: `class` as AnyObject) | ||
return `class`.withCString { | ||
return _isImpl(object, kindOf: $0) | ||
} | ||
} | ||
|
||
internal func _getClassPlaygroundQuickLook( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -171,6 +171,10 @@ internal protocol _NSSetCore: _NSCopying, _NSFastEnumeration { | |
/// supplies. | ||
@unsafe_no_objc_tagged_pointer @objc | ||
internal protocol _NSSet: _NSSetCore { | ||
@objc(getObjects:count:) func getObjects( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be a small speedup relative to the CF call :) |
||
_ buffer: UnsafeMutablePointer<AnyObject>, | ||
count: Int | ||
) | ||
} | ||
|
||
/// A shadow for the API of NSNumber we will use in the core | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,14 +42,28 @@ | |
using namespace swift; | ||
using namespace swift::hashable_support; | ||
|
||
// Mimic the memory layout of NSError so things don't go haywire when we | ||
// switch superclasses to the real thing. | ||
Catfish-Man marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@interface __SwiftNSErrorLayoutStandin : NSObject { | ||
@private | ||
void *_reserved; | ||
NSInteger _code; | ||
id _domain; | ||
id _userInfo; | ||
} | ||
@end | ||
|
||
@implementation __SwiftNSErrorLayoutStandin | ||
@end | ||
|
||
/// A subclass of NSError used to represent bridged native Swift errors. | ||
/// This type cannot be subclassed, and should not ever be instantiated | ||
/// except by the Swift runtime. | ||
/// | ||
/// NOTE: older runtimes called this _SwiftNativeNSError. The two must | ||
/// coexist, so it was renamed. The old name must not be used in the new | ||
/// runtime. | ||
@interface __SwiftNativeNSError : NSError | ||
@interface __SwiftNativeNSError : __SwiftNSErrorLayoutStandin | ||
@end | ||
|
||
@implementation __SwiftNativeNSError | ||
|
@@ -71,29 +85,29 @@ - (void)dealloc { | |
// layout. This gives us a buffer in case NSError decides to change its stored | ||
// property order. | ||
|
||
- (NSString*)domain { | ||
- (id /* NSString */)domain { | ||
auto error = (const SwiftError*)self; | ||
// The domain string should not be nil; if it is, then this error box hasn't | ||
// been initialized yet as an NSError. | ||
auto domain = error->domain.load(SWIFT_MEMORY_ORDER_CONSUME); | ||
assert(domain | ||
&& "Error box used as NSError before initialization"); | ||
// Don't need to .retain.autorelease since it's immutable. | ||
return cf_const_cast<NSString*>(domain); | ||
return cf_const_cast<id>(domain); | ||
} | ||
|
||
- (NSInteger)code { | ||
auto error = (const SwiftError*)self; | ||
return error->code.load(SWIFT_MEMORY_ORDER_CONSUME); | ||
} | ||
|
||
- (NSDictionary*)userInfo { | ||
- (id /* NSDictionary */)userInfo { | ||
auto error = (const SwiftError*)self; | ||
auto userInfo = error->userInfo.load(SWIFT_MEMORY_ORDER_CONSUME); | ||
assert(userInfo | ||
&& "Error box used as NSError before initialization"); | ||
// Don't need to .retain.autorelease since it's immutable. | ||
return cf_const_cast<NSDictionary*>(userInfo); | ||
return cf_const_cast<id>(userInfo); | ||
} | ||
|
||
- (id)copyWithZone:(NSZone *)zone { | ||
|
@@ -153,16 +167,29 @@ - (BOOL)isEqual:(id)other { | |
@end | ||
|
||
Class swift::getNSErrorClass() { | ||
return SWIFT_LAZY_CONSTANT([NSError class]); | ||
return SWIFT_LAZY_CONSTANT(objc_lookUpClass("NSError")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there still a difference between this and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One small difference: they pass different values to the third, unused parameter of the internal function that implements the functionality. Don't ask me why. |
||
} | ||
|
||
const Metadata *swift::getNSErrorMetadata() { | ||
return SWIFT_LAZY_CONSTANT( | ||
swift_getObjCClassMetadata((const ClassMetadata *)getNSErrorClass())); | ||
} | ||
|
||
static Class getAndBridgeSwiftNativeNSErrorClass() { | ||
Class nsErrorClass = swift::getNSErrorClass(); | ||
Class ourClass = [__SwiftNativeNSError class]; | ||
// We want "err as AnyObject" to do *something* even without Foundation | ||
if (nsErrorClass) { | ||
#pragma clang diagnostic push | ||
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | ||
class_setSuperclass(ourClass, nsErrorClass); | ||
#pragma clang diagnostic pop | ||
} | ||
return ourClass; | ||
} | ||
|
||
static Class getSwiftNativeNSErrorClass() { | ||
return SWIFT_LAZY_CONSTANT([__SwiftNativeNSError class]); | ||
return SWIFT_LAZY_CONSTANT(getAndBridgeSwiftNativeNSErrorClass()); | ||
} | ||
|
||
/// Allocate a catchable error object. | ||
|
@@ -274,7 +301,7 @@ static ErrorBridgingInfo getErrorBridgingInfo() { | |
|
||
const Metadata *SwiftError::getType() const { | ||
if (isPureNSError()) { | ||
auto asError = reinterpret_cast<NSError *>(const_cast<SwiftError *>(this)); | ||
id asError = reinterpret_cast<id>(const_cast<SwiftError *>(this)); | ||
return swift_getObjCClassMetadata((ClassMetadata*)[asError class]); | ||
} | ||
return type; | ||
|
@@ -363,9 +390,9 @@ static ErrorBridgingInfo getErrorBridgingInfo() { | |
#define getErrorDomainNSString \ | ||
MANGLE_SYM(s23_getErrorDomainNSStringyyXlSPyxGs0B0RzlF) | ||
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL | ||
NSString *getErrorDomainNSString(const OpaqueValue *error, | ||
const Metadata *T, | ||
const WitnessTable *Error); | ||
id getErrorDomainNSString(const OpaqueValue *error, | ||
const Metadata *T, | ||
const WitnessTable *Error); | ||
|
||
// internal func _getErrorCode<T : Error>(_ x: UnsafePointer<T>) -> Int | ||
#define getErrorCode \ | ||
|
@@ -379,17 +406,17 @@ NSInteger getErrorCode(const OpaqueValue *error, | |
#define getErrorUserInfoNSDictionary \ | ||
MANGLE_SYM(s29_getErrorUserInfoNSDictionaryyyXlSgSPyxGs0B0RzlF) | ||
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL | ||
NSDictionary *getErrorUserInfoNSDictionary( | ||
id getErrorUserInfoNSDictionary( | ||
const OpaqueValue *error, | ||
const Metadata *T, | ||
const WitnessTable *Error); | ||
|
||
// @_silgen_name("_swift_stdlib_getErrorDefaultUserInfo") | ||
// internal func _getErrorDefaultUserInfo<T : Error>(_ x: T) -> AnyObject | ||
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL | ||
NSDictionary *_swift_stdlib_getErrorDefaultUserInfo(OpaqueValue *error, | ||
const Metadata *T, | ||
const WitnessTable *Error) { | ||
id _swift_stdlib_getErrorDefaultUserInfo(OpaqueValue *error, | ||
const Metadata *T, | ||
const WitnessTable *Error) { | ||
// public func Foundation._getErrorDefaultUserInfo<T: Error>(_ error: T) | ||
// -> AnyObject? | ||
auto foundationGetDefaultUserInfo = getErrorBridgingInfo().GetErrorDefaultUserInfo; | ||
|
@@ -407,7 +434,7 @@ NSInteger getErrorCode(const OpaqueValue *error, | |
/// at +1. | ||
id | ||
swift::_swift_stdlib_bridgeErrorToNSError(SwiftError *errorObject) { | ||
auto ns = reinterpret_cast<NSError *>(errorObject); | ||
id ns = reinterpret_cast<id>(errorObject); | ||
|
||
// If we already have a domain set, then we've already initialized. | ||
// If this is a real NSError, then Cocoa and Core Foundation's initializers | ||
|
@@ -431,9 +458,9 @@ NSInteger getErrorCode(const OpaqueValue *error, | |
auto type = errorObject->getType(); | ||
auto witness = errorObject->getErrorConformance(); | ||
|
||
NSString *domain = getErrorDomainNSString(value, type, witness); | ||
id domain = getErrorDomainNSString(value, type, witness); | ||
NSInteger code = getErrorCode(value, type, witness); | ||
NSDictionary *userInfo = getErrorUserInfoNSDictionary(value, type, witness); | ||
id userInfo = getErrorUserInfoNSDictionary(value, type, witness); | ||
|
||
// Never produce an empty userInfo dictionary. | ||
if (!userInfo) | ||
|
@@ -480,7 +507,7 @@ NSInteger getErrorCode(const OpaqueValue *error, | |
if (![reinterpret_cast<id>(object) isKindOfClass: NSErrorClass]) | ||
return false; | ||
|
||
NSError *srcInstance = reinterpret_cast<NSError *>(object); | ||
id srcInstance = reinterpret_cast<id>(object); | ||
|
||
// A __SwiftNativeNSError box can always be unwrapped to cast the value back | ||
// out as an Error existential. | ||
|
@@ -523,7 +550,7 @@ ProtocolDescriptorRef theErrorProtocol(&PROTOCOL_DESCR_SYM(s5Error), | |
auto *destTypeExistential = dyn_cast<ExistentialTypeMetadata>(destType); | ||
if (destTypeExistential && | ||
destTypeExistential->getRepresentation() == ExistentialTypeRepresentation::Error) { | ||
auto destBoxAddr = reinterpret_cast<NSError**>(dest); | ||
auto destBoxAddr = reinterpret_cast<id*>(dest); | ||
*destBoxAddr = objc_retain(srcInstance); | ||
return true; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any mechanism or way to enforce this? Requiring maintainers to just remember to bind a
let
variable whenever they bridge seems very error prone and would lead to difficult to understand bugs.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only needed for the set of classes that we use the reparenting mechanism for, not bridging in general. So it shouldn't need to expand much beyond this, if any.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spent a while brainstorming ways to enforce this but haven't come up with much so far. The problem is that it's an easy property to check, but once it's set, you lose the ability to check if other code paths set it. So we'd need to somehow have tests that exercised each possible future bridging function before bridging anything else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly more practical might be debug preconditions that check the superclass at a funnel point in each class, but I'm not sure there are good funnel points
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably a horrible idea (I can probably stop qualifying my statements this way, my username is sufficient warning by now) but how about, say, overriding
retain
on these classes in debug builds and checking there? That comes pretty close to a funnel point that only happens when bridging.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
objectAtIndex:
orcount
might be a slightly less horrible funnel point, at the cost of "if someone calls something else implemented on NSArray first it will just crash".There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not the main issue with this approach. This patch makes it so that anyone who wants to instantiate a type, such as
__SwiftEmptyNSEnumerator
, has to somehow know the unwritten convention that they need to call_connectOrphanedFoundationSubclassesIfNeeded()
first, or else there will be obscure corner case bugs discovered in production.For example, a more structured approach would be for e.g.
__SwiftNativeNSEnumerator
's initializer to call_connectOrphanedFoundationSubclassesIfNeeded()
. That way, at least for that type family, it is an enforced invariant rather than an ad-hoc use-site convention. Is there something preventing this?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially thought moving it to init would require adding an override of init, which would add an extra objc_msgSend to every initialization of these, but it looks like we have an empty passthrough override anyway.
Even better though: Jordan points out in another comment that you can't get to this except via another bridging call, which will do this initialization, so we can just delete the enumerator calls entirely.