-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Turn on ‘as’ bridging on Linux. #16022
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 |
---|---|---|
|
@@ -10,7 +10,6 @@ | |
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#if _runtime(_ObjC) | ||
/// A Swift Array or Dictionary of types conforming to | ||
/// `_ObjectiveCBridgeable` can be passed to Objective-C as an NSArray or | ||
/// NSDictionary, respectively. The elements of the resulting NSArray | ||
|
@@ -83,6 +82,8 @@ public protocol _ObjectiveCBridgeable { | |
-> Self | ||
} | ||
|
||
#if _runtime(_ObjC) | ||
|
||
//===--- Bridging for metatypes -------------------------------------------===// | ||
|
||
/// A stand-in for a value of metatype type. | ||
|
@@ -640,3 +641,200 @@ public func _getObjCTypeEncoding<T>(_ type: T.Type) -> UnsafePointer<Int8> { | |
} | ||
|
||
#endif | ||
|
||
//===--- Bridging without the ObjC runtime --------------------------------===// | ||
|
||
#if !_runtime(_ObjC) | ||
|
||
/// Convert `x` from its Objective-C representation to its Swift | ||
/// representation. | ||
/// COMPILER_INTRINSIC | ||
@_inlineable // FIXME(sil-serialize-all) | ||
public func _forceBridgeFromObjectiveC_bridgeable<T:_ObjectiveCBridgeable> ( | ||
_ x: T._ObjectiveCType, | ||
_: T.Type | ||
) -> T { | ||
var result: T? | ||
T._forceBridgeFromObjectiveC(x, result: &result) | ||
return result! | ||
} | ||
|
||
/// Attempt to convert `x` from its Objective-C representation to its Swift | ||
/// representation. | ||
/// COMPILER_INTRINSIC | ||
@_inlineable // FIXME(sil-serialize-all) | ||
public func _conditionallyBridgeFromObjectiveC_bridgeable<T:_ObjectiveCBridgeable>( | ||
_ x: T._ObjectiveCType, | ||
_: T.Type | ||
) -> T? { | ||
var result: T? | ||
T._conditionallyBridgeFromObjectiveC (x, result: &result) | ||
return result | ||
} | ||
|
||
public // SPI(Foundation) | ||
protocol _NSSwiftValue: class { | ||
init(_ value: Any) | ||
var value: Any { get } | ||
static var null: AnyObject { get } | ||
} | ||
|
||
@usableFromInline | ||
internal class _SwiftValue { | ||
@usableFromInline | ||
let value: Any | ||
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. You could avoid double-indirecting using a small class hierarchy:
This might also let you represent null as a singleton subclass instead of as a wrapper around a specific kind of 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. We cannot reparent |
||
|
||
@usableFromInline | ||
init(_ value: Any) { | ||
self.value = value | ||
} | ||
|
||
@usableFromInline | ||
static let null = _SwiftValue(Optional<Any>.none as Any) | ||
} | ||
|
||
/// COMPILER_INTRISIC | ||
@_silgen_name("swift_unboxFromSwiftValueWithType") | ||
public func swift_unboxFromSwiftValueWithType<T>( | ||
_ source: inout AnyObject, | ||
_ result: UnsafeMutablePointer<T> | ||
) -> Bool { | ||
|
||
if source === _nullPlaceholder { | ||
if let unpacked = Optional<Any>.none as? T { | ||
result.initialize(to: unpacked) | ||
return true | ||
} | ||
} | ||
|
||
if let box = source as? _SwiftValue { | ||
if let value = box.value as? T { | ||
result.initialize(to: value) | ||
return true | ||
} | ||
} else if let box = source as? _NSSwiftValue { | ||
if let value = box.value as? T { | ||
result.initialize(to: value) | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
@_silgen_name("_swift_extractDynamicValue") | ||
public func _extractDynamicValue<T>(_ value: T) -> AnyObject? | ||
|
||
@_silgen_name("_swift_bridgeToObjectiveCUsingProtocolIfPossible") | ||
public func _bridgeToObjectiveCUsingProtocolIfPossible<T>(_ value: T) -> AnyObject? | ||
|
||
@usableFromInline | ||
protocol _Unwrappable { | ||
func unwrap() -> Any? | ||
} | ||
|
||
extension Optional: _Unwrappable { | ||
func unwrap() -> Any? { | ||
return self | ||
} | ||
} | ||
|
||
// This is a best-effort tripmine for detecting the situation | ||
// (which should never happen) of Swift._SwiftValue and | ||
// Foundation._SwiftValue/Foundation.NSNull being used | ||
// in the same process. | ||
|
||
@usableFromInline | ||
internal enum _SwiftValueFlavor: Equatable { | ||
case stdlib | ||
case foundation | ||
} | ||
|
||
@usableFromInline | ||
func _currentSwiftValueFlavor() -> _SwiftValueFlavor { | ||
if _typeByName("Foundation._SwiftValue") as? _NSSwiftValue.Type != nil { | ||
return .foundation | ||
} else { | ||
return .stdlib | ||
} | ||
} | ||
|
||
@usableFromInline | ||
internal var _selectedSwiftValueFlavor: _SwiftValueFlavor = _currentSwiftValueFlavor() | ||
|
||
@usableFromInline | ||
internal func _assertSwiftValueFlavorIsConsistent() { | ||
assert(_selectedSwiftValueFlavor == _currentSwiftValueFlavor()) | ||
} | ||
|
||
@usableFromInline | ||
internal var _nullPlaceholder: AnyObject { | ||
_assertSwiftValueFlavorIsConsistent() | ||
if let foundationType = _typeByName("Foundation._SwiftValue") as? _NSSwiftValue.Type { | ||
return foundationType.null | ||
} else { | ||
return _SwiftValue.null | ||
} | ||
} | ||
|
||
@usableFromInline | ||
func _makeSwiftValue(_ value: Any) -> AnyObject { | ||
_assertSwiftValueFlavorIsConsistent() | ||
if let foundationType = _typeByName("Foundation._SwiftValue") as? _NSSwiftValue.Type { | ||
return foundationType.init(value) | ||
} else { | ||
return _SwiftValue(value) | ||
} | ||
} | ||
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 a plan to retire 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. The problem is that _SwiftValue is an NSObject subclass on Apple platforms, and we probably can't undo that. 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. How does that become important in practice? We don't promise that on Darwin even, we "just haven't gotten around" to making it an independent root class. 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. It becomes important when it interacts with CF API. Right now, calling CFCopy() will go through the NSCopying conformance that all SwiftValues implicitly have to, and that is still possible on Linux as well (you can toll-free bridge swift-corelibs-foundation’s NSArray/Dictionary to CFArray/DictionaryRef the same way you can on Darwin, and these may contain _SwiftValues and be set up to copy.) 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. (It is also secondarily important for two reasons:
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. At a high level, I don't think changing the boxing behavior dynamically based on the presence of Foundation is workable—what happens when a program that doesn't use Foundation dynamically loads a library that does? If we really must make boxed values be NSObject subclasses when Foundation is loaded, we should look into doing so in a way that doesn't invalidate boxed values that exist before Foundation is loaded. For instance, we could make it so that boxing uses heap objects that start out with non-class heap metadata, and overwrite the metadata with an NSObject subclass when Foundation gets loaded. |
||
|
||
/// Bridge an arbitrary value to an Objective-C object. | ||
/// | ||
/// - If `T` is a class type, it is always bridged verbatim, the function | ||
/// returns `x`; | ||
/// | ||
/// - otherwise, if `T` conforms to `_ObjectiveCBridgeable`, | ||
/// returns the result of `x._bridgeToObjectiveC()`; | ||
/// | ||
/// - otherwise, we use **boxing** to bring the value into Objective-C. | ||
/// The value is wrapped in an instance of a private Objective-C class | ||
/// that is `id`-compatible and dynamically castable back to the type of | ||
/// the boxed value, but is otherwise opaque. | ||
/// | ||
/// COMPILER_INTRINSIC | ||
@inlinable // FIXME(sil-serialize-all) | ||
public func _bridgeAnythingToObjectiveC<T>(_ x: T) -> AnyObject { | ||
var done = false | ||
var result: AnyObject! | ||
|
||
var source: Any = x | ||
|
||
if let dynamicSource = _extractDynamicValue(x) { | ||
result = dynamicSource as AnyObject | ||
done = true | ||
} | ||
|
||
if !done, let wrapper = source as? _Unwrappable { | ||
if let value = wrapper.unwrap() { | ||
result = value as AnyObject | ||
} else { | ||
result = _nullPlaceholder | ||
} | ||
|
||
done = true | ||
} | ||
|
||
if !done { | ||
if type(of: source) as? AnyClass != nil { | ||
result = unsafeBitCast(x, to: AnyObject.self) | ||
} else if let object = _bridgeToObjectiveCUsingProtocolIfPossible(source) { | ||
result = object | ||
} else { | ||
result = _makeSwiftValue(source) | ||
} | ||
} | ||
|
||
return result | ||
} | ||
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. I feel like this would be best written in C++ to have full access to the low-level metadata APIs. Keeping the entire implementation would be cleaner, and probably lead to more understandable code, than doing the oblique things you need to approximate this in Swift slowly and opaquely, such as sprinkling new protocols like 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. Part of why I went with Swift over C++ was the issue of correctness of memory management; is there documentation on the expectations and SPI? My understanding is that with the current work the documentation in the repository may no longer apply. 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. The main thing that would've changed since then is that Swift functions now take arguments +0 by default (and we have macros to try to handle both +1 and +0 conventions as a transitional/fallback mechanism, though I'm not sure whether that's important for new code, cc @gottesmm). The Darwin implementation of bridging lives largely in the C++ runtime, and we have fairly extensive runtime tests that you should be able to gradually enable that check for object lifetime issues. |
||
|
||
#endif // !_runtime(_ObjC) | ||
|
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.
Do we try to build the ObjectiveC module on non-ObjC-interop platforms? I didn't see a CMakeLists.txt change to that effect.
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 can revert this if it’s unneeded. Part of the debugging cycle for this patch was building it on Darwin simulating the Linux situation so that I could iterate on it with Xcode; it may be a leftover from that.