mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
571 lines
17 KiB
Swift
571 lines
17 KiB
Swift
//===--- ArrayBridge.swift - Tests of Array casting and bridging ----------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// RUN: %empty-directory(%t)
|
|
//
|
|
// RUN: %gyb %s -o %t/ArrayBridge.swift
|
|
// RUN: %target-clang %S/Inputs/ArrayBridge/ArrayBridge.m -c -o %t/ArrayBridgeObjC.o -g
|
|
// RUN: %line-directive %t/ArrayBridge.swift -- %target-build-swift %t/ArrayBridge.swift -I %S/Inputs/ArrayBridge/ -Xlinker %t/ArrayBridgeObjC.o -o %t/ArrayBridge -swift-version 4.2 --
|
|
|
|
// RUN: %target-codesign %t/ArrayBridge
|
|
// RUN: %target-run %t/ArrayBridge
|
|
// REQUIRES: executable_test
|
|
// REQUIRES: objc_interop
|
|
|
|
// Temporarily disabled on arm64 (rdar://128522114)
|
|
// UNSUPPORTED: CPU=arm64
|
|
|
|
// And on older OS (rdar://132941962)
|
|
// UNSUPPORTED: use_os_stdlib
|
|
|
|
import Foundation
|
|
import ArrayBridgeObjC
|
|
import StdlibUnittest
|
|
let tests = TestSuite("ArrayBridge")
|
|
|
|
var trackedCount = 0
|
|
var nextBaseSerialNumber = 0
|
|
|
|
@objc protocol Fooable {
|
|
func foo()
|
|
}
|
|
|
|
@objc protocol Barable {
|
|
func bar()
|
|
}
|
|
|
|
@objc protocol Bazable {
|
|
func baz()
|
|
}
|
|
|
|
/// A type that will be bridged verbatim to Objective-C
|
|
class Base : NSObject, Fooable {
|
|
func foo() { }
|
|
|
|
required init(_ value: Int) {
|
|
trackedCount += 1
|
|
nextBaseSerialNumber += 1
|
|
serialNumber = nextBaseSerialNumber
|
|
self.value = value
|
|
}
|
|
|
|
deinit {
|
|
assert(serialNumber > 0, "double destruction!")
|
|
trackedCount -= 1
|
|
serialNumber = -serialNumber
|
|
}
|
|
|
|
override func isEqual(_ other: Any?) -> Bool {
|
|
return (other as? Base)?.value == self.value
|
|
}
|
|
|
|
var value: Int
|
|
var serialNumber: Int
|
|
}
|
|
|
|
class Subclass : Base, Barable {
|
|
func bar() { }
|
|
}
|
|
|
|
var bridgeFromOperationCount = 0
|
|
var bridgeToOperationCount = 0
|
|
|
|
/// A value type that's bridged using _ObjectiveCBridgeable
|
|
struct BridgeableValue : _ObjectiveCBridgeable, Equatable {
|
|
func _bridgeToObjectiveC() -> Subclass {
|
|
bridgeToOperationCount += 1
|
|
return Subclass(trak.value)
|
|
}
|
|
|
|
static func _forceBridgeFromObjectiveC(
|
|
_ x: Subclass,
|
|
result: inout BridgeableValue?
|
|
) {
|
|
assert(x.value >= 0, "not bridged")
|
|
bridgeFromOperationCount += 1
|
|
result = BridgeableValue(x.value)
|
|
}
|
|
|
|
static func _conditionallyBridgeFromObjectiveC(
|
|
_ x: Subclass,
|
|
result: inout BridgeableValue?
|
|
) -> Bool {
|
|
if x.value >= 0 {
|
|
result = BridgeableValue(x.value)
|
|
return true
|
|
}
|
|
|
|
result = nil
|
|
return false
|
|
}
|
|
|
|
static func _unconditionallyBridgeFromObjectiveC(_ source: Subclass?)
|
|
-> BridgeableValue {
|
|
var result: BridgeableValue?
|
|
_forceBridgeFromObjectiveC(source!, result: &result)
|
|
return result!
|
|
}
|
|
|
|
init(_ value: Int) {
|
|
self.trak = Base(value)
|
|
}
|
|
|
|
static func resetStats() {
|
|
bridgeFromOperationCount = 0
|
|
bridgeToOperationCount = 0
|
|
}
|
|
|
|
var trak: Base
|
|
}
|
|
|
|
func == (lhs: BridgeableValue, rhs: BridgeableValue) -> Bool {
|
|
return lhs.trak.value == rhs.trak.value
|
|
}
|
|
|
|
// A class used to test various Objective-C thunks.
|
|
class Thunks : NSObject {
|
|
@objc func createSubclass(_ value: Int) -> AnyObject {
|
|
return Subclass(value)
|
|
}
|
|
|
|
@objc func acceptSubclassArray(
|
|
_ x: [Subclass],
|
|
expecting expected: NSArray
|
|
) {
|
|
expectEqualSequence(
|
|
expected.lazy.map { ObjectIdentifier($0 as AnyObject) },
|
|
x.lazy.map { ObjectIdentifier($0 as AnyObject) }
|
|
)
|
|
}
|
|
|
|
@objc func produceSubclassArray(
|
|
_ expectations: NSMutableArray
|
|
) -> [Subclass] {
|
|
var array: [Subclass] = []
|
|
for i in 0..<5 {
|
|
array.append(Subclass(i))
|
|
expectations.add(array[i])
|
|
}
|
|
return array
|
|
}
|
|
|
|
@objc func checkProducedSubclassArray(
|
|
_ produced: NSArray, expecting expected: NSArray
|
|
) {
|
|
expectEqualSequence(
|
|
expected.lazy.map { ObjectIdentifier($0 as AnyObject) },
|
|
produced.lazy.map { ObjectIdentifier($0 as AnyObject) }
|
|
)
|
|
}
|
|
|
|
@objc func acceptBridgeableValueArray(_ raw: NSArray) {
|
|
let x = raw as! [BridgeableValue]
|
|
expectEqualSequence(
|
|
raw.lazy.map { $0 as! BridgeableValue },
|
|
x
|
|
)
|
|
}
|
|
|
|
@objc func produceBridgeableValueArray() -> NSArray {
|
|
var array: [BridgeableValue] = []
|
|
for i in 0..<5 {
|
|
array.append(BridgeableValue(i))
|
|
}
|
|
return array as NSArray
|
|
}
|
|
|
|
@objc func checkProducedBridgeableValueArray(_ produced: NSArray) {
|
|
expectEqualSequence(
|
|
0..<5,
|
|
produced.lazy.map { ($0 as! Subclass).value })
|
|
}
|
|
}
|
|
|
|
|
|
//===--- Bridged Verbatim -------------------------------------------------===//
|
|
// Base is "bridged verbatim"
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
tests.test("testBridgedVerbatim") {
|
|
nextBaseSerialNumber = 0
|
|
let bases: [Base] = [Base(100), Base(200), Base(300)]
|
|
|
|
//===--- Implicit conversion to/from NSArray ------------------------------===//
|
|
|
|
let basesConvertedToNSArray = bases as NSArray
|
|
let firstBase = basesConvertedToNSArray.object(at: 0) as! Base
|
|
expectEqual(100, firstBase.value)
|
|
expectEqual(1, firstBase.serialNumber)
|
|
|
|
// Verify that NSArray class methods are inherited by a Swift bridging class.
|
|
let className = String(reflecting: type(of: basesConvertedToNSArray))
|
|
expectTrue(className.hasPrefix("Swift._ContiguousArrayStorage"))
|
|
expectTrue(type(of: basesConvertedToNSArray).supportsSecureCoding)
|
|
|
|
//===--- Up- and Down-casts -----------------------------------------------===//
|
|
var subclass: [Subclass] = [Subclass(11), Subclass(22)]
|
|
let subclass0 = subclass
|
|
|
|
// upcast is implicit
|
|
let subclassAsBases: [Base] = subclass
|
|
expectEqual(subclass.count, subclassAsBases.count)
|
|
for (x, y) in zip(subclass, subclassAsBases) {
|
|
expectTrue(x === y)
|
|
}
|
|
|
|
// Arrays are logically distinct after upcast
|
|
subclass[0] = Subclass(33)
|
|
|
|
expectEqual([Subclass(33), Subclass(22)], subclass)
|
|
expectEqual([Subclass(11), Subclass(22)], subclassAsBases)
|
|
|
|
expectEqual(subclass0, subclassAsBases as! [Subclass])
|
|
|
|
}
|
|
|
|
% for Any in 'Any', 'AnyObject':
|
|
|
|
tests.test("Another/${Any}") {
|
|
// Create an ordinary NSArray, not a native one
|
|
let nsArrayOfBase: NSArray = NSArray(object: Base(42))
|
|
|
|
// NSArray can be unconditionally cast to [${Any}]...
|
|
|
|
let nsArrayOfBaseConvertedToAnyArray = nsArrayOfBase
|
|
% if Any == 'Any':
|
|
// FIXME: nsArrayOfBase as [Any] doesn't
|
|
// typecheck.
|
|
as [AnyObject]
|
|
% end
|
|
as [${Any}]
|
|
|
|
// Capture the representation of the first element
|
|
let base42: ObjectIdentifier
|
|
do {
|
|
let b = nsArrayOfBase.object(at: 0) as! Base
|
|
expectEqual(42, b.value)
|
|
base42 = ObjectIdentifier(b)
|
|
}
|
|
|
|
// ...with the same elements
|
|
expectEqual(
|
|
base42,
|
|
ObjectIdentifier(nsArrayOfBaseConvertedToAnyArray[0] as! Base))
|
|
|
|
let subclassInBaseBuffer: [Base] = [Subclass(44), Subclass(55)]
|
|
let subclass2 = subclassInBaseBuffer
|
|
|
|
// Explicit downcast-ability is based on element type, not buffer type
|
|
expectNotNil(subclassInBaseBuffer as? [Subclass])
|
|
|
|
// We can up-cast to array of Any
|
|
let subclassAsAnyArray: [${Any}] = subclassInBaseBuffer
|
|
expectEqual(subclass2, subclassAsAnyArray.map { $0 as! Base })
|
|
|
|
let downcastBackToBase = subclassAsAnyArray as? [Base]
|
|
expectNotNil(downcastBackToBase)
|
|
|
|
if let downcastBackToSubclass = expectNotNil(subclassAsAnyArray as? [Subclass]) {
|
|
expectEqual(subclass2, downcastBackToSubclass)
|
|
}
|
|
|
|
if let downcastToProtocols = expectNotNil(subclassAsAnyArray as? [Fooable]) {
|
|
expectEqual(subclass2, downcastToProtocols.map { $0 as! Subclass })
|
|
}
|
|
|
|
if let downcastToProtocols = expectNotNil(subclassAsAnyArray as? [Barable]) {
|
|
expectEqual(subclass2, downcastToProtocols.map { $0 as! Subclass })
|
|
}
|
|
|
|
if let downcastToProtocols = expectNotNil(subclassAsAnyArray as? [Barable & Fooable]) {
|
|
expectEqual(subclass2, downcastToProtocols.map { $0 as! Subclass })
|
|
}
|
|
|
|
expectNil(subclassAsAnyArray as? [Barable & Bazable])
|
|
}
|
|
|
|
//===--- Explicitly Bridged -----------------------------------------------===//
|
|
// BridgeableValue conforms to _ObjectiveCBridgeable
|
|
//===----------------------------------------------------------------------===//
|
|
tests.test("testExplicitlyBridged/${Any}") {
|
|
|
|
let bridgeableValues = [BridgeableValue(42), BridgeableValue(17)]
|
|
|
|
let bridgeableValuesAsNSArray = bridgeableValues as NSArray
|
|
expectEqual(2, bridgeableValuesAsNSArray.count)
|
|
expectEqual(42, (bridgeableValuesAsNSArray[0] as! Subclass).value)
|
|
expectEqual(17, (bridgeableValuesAsNSArray[1] as! Subclass).value)
|
|
|
|
// Make sure we can bridge back.
|
|
let roundTrippedValues = Swift._forceBridgeFromObjectiveC(
|
|
bridgeableValuesAsNSArray, [BridgeableValue].self)
|
|
|
|
// Expect equal values...
|
|
expectEqualSequence(bridgeableValues, roundTrippedValues)
|
|
// ...with the same identity for some reason? FIXME: understand why.
|
|
expectFalse(
|
|
zip(bridgeableValues, roundTrippedValues).contains { $0.trak !== $1.trak })
|
|
|
|
// Make a real Cocoa NSArray of these...
|
|
let cocoaBridgeableValues = NSArray(
|
|
array: bridgeableValuesAsNSArray
|
|
%if Any == 'Any':
|
|
// FIXME: should just be "as [Any]" but the typechecker doesn't allow it
|
|
// yet.
|
|
as [AnyObject] as [Any] as [AnyObject]
|
|
%else:
|
|
as [${Any}]
|
|
%end
|
|
)
|
|
|
|
// ...and bridge *that* back
|
|
let bridgedBackSwifts = Swift._forceBridgeFromObjectiveC(
|
|
cocoaBridgeableValues, [BridgeableValue].self)
|
|
|
|
// Expect equal, but distinctly created instances
|
|
expectEqualSequence(bridgeableValues, bridgedBackSwifts)
|
|
expectFalse(
|
|
zip(bridgedBackSwifts, roundTrippedValues).contains { $0.trak === $1.trak })
|
|
expectFalse(
|
|
zip(bridgedBackSwifts, bridgeableValues).contains { $0.trak === $1.trak })
|
|
|
|
let expectedSubclasses = [Subclass(42), Subclass(17)]
|
|
let expectedBases = expectedSubclasses.lazy.map { $0 as Base }
|
|
|
|
// Up-casts.
|
|
let bridgeableValuesAsSubclasses = bridgeableValues as [Subclass]
|
|
expectEqualSequence(expectedSubclasses, bridgeableValuesAsSubclasses)
|
|
|
|
let bridgeableValuesAsBases = bridgeableValues as [Base]
|
|
expectEqualSequence(expectedBases, bridgeableValuesAsBases)
|
|
|
|
let bridgeableValuesAsAnys = bridgeableValues as [Any]
|
|
expectEqualSequence(
|
|
expectedBases,
|
|
bridgeableValuesAsAnys.lazy.map { $0 as! Base })
|
|
|
|
// Downcasts of non-verbatim bridged value types to objects.
|
|
do {
|
|
let downcasted = bridgeableValues as [Subclass]
|
|
expectEqualSequence(expectedSubclasses, downcasted)
|
|
}
|
|
|
|
do {
|
|
let downcasted = bridgeableValues as [Base]
|
|
expectEqualSequence(expectedBases, downcasted)
|
|
}
|
|
|
|
do {
|
|
let downcasted = bridgeableValues as [${Any}]
|
|
expectEqualSequence(expectedBases, downcasted.map { $0 as! Base })
|
|
}
|
|
|
|
// Downcasts of up-casted arrays.
|
|
if let downcasted = expectNotNil(
|
|
bridgeableValuesAsAnys as? [Subclass]
|
|
) {
|
|
expectEqualSequence(expectedSubclasses, downcasted)
|
|
}
|
|
|
|
if let downcasted = bridgeableValuesAsAnys as? [Base] {
|
|
expectEqualSequence(expectedBases, downcasted)
|
|
}
|
|
|
|
// Downcast of Cocoa array to an array of classes.
|
|
let wrappedCocoaBridgeableValues = cocoaBridgeableValues
|
|
%if Any == 'Any':
|
|
// FIXME: should just be "as [Any]" but typechecker doesn't allow it
|
|
// yet.
|
|
as [AnyObject]
|
|
%end
|
|
as [${Any}]
|
|
|
|
if let downcasted = wrappedCocoaBridgeableValues as? [Subclass] {
|
|
expectEqualSequence(expectedSubclasses, downcasted)
|
|
}
|
|
|
|
// Downcast of Cocoa array to an array of values.
|
|
if let downcasted = wrappedCocoaBridgeableValues as? [BridgeableValue] {
|
|
expectEqualSequence(bridgeableValues, downcasted)
|
|
}
|
|
|
|
// Downcast of Cocoa array to an array of strings (which should fail)
|
|
expectNil(wrappedCocoaBridgeableValues as? [String])
|
|
|
|
// Downcast from an implicitly unwrapped optional array of Anys.
|
|
var wrappedCocoaBridgeableValuesIUO: [${Any}]! = wrappedCocoaBridgeableValues
|
|
if let downcasted = wrappedCocoaBridgeableValuesIUO as? [BridgeableValue] {
|
|
expectEqualSequence(bridgeableValues, downcasted)
|
|
}
|
|
|
|
// Downcast from a nil implicitly unwrapped optional array of Anys.
|
|
wrappedCocoaBridgeableValuesIUO = nil
|
|
expectNil(wrappedCocoaBridgeableValuesIUO as? [BridgeableValue])
|
|
|
|
// Downcast from an optional array of Anys.
|
|
var wrappedCocoaBridgeableValuesOpt: [${Any}]? = wrappedCocoaBridgeableValues
|
|
if let downcasted = wrappedCocoaBridgeableValuesOpt as? [BridgeableValue] {
|
|
expectEqualSequence(bridgeableValues, downcasted)
|
|
}
|
|
|
|
// Downcast from a nil optional array of Anys.
|
|
wrappedCocoaBridgeableValuesOpt = nil
|
|
expectNil(wrappedCocoaBridgeableValuesOpt as? [BridgeableValue])
|
|
}
|
|
% end
|
|
|
|
tests.test("testThunks") {
|
|
testSubclass(Thunks())
|
|
testBridgeableValue(Thunks())
|
|
}
|
|
|
|
tests.test("testHKTFilter") {
|
|
let base = ["hello", "world"]
|
|
let result = testHKTFilter(base) as! NSArray as! [String]
|
|
expectEqual(result, ["hello"])
|
|
}
|
|
|
|
tests.test("testRoundTrip") {
|
|
class Test : NSObject {
|
|
@objc dynamic func call(_ array: NSArray) -> NSArray {
|
|
|
|
let result = array as! [BridgeableValue]
|
|
expectEqual(0, bridgeFromOperationCount)
|
|
expectEqual(0, bridgeToOperationCount)
|
|
|
|
// Clear out the stats before returning array
|
|
BridgeableValue.resetStats()
|
|
return result as NSArray
|
|
}
|
|
}
|
|
|
|
let test = Test()
|
|
|
|
let array = [
|
|
BridgeableValue(10), BridgeableValue(20), BridgeableValue(30),
|
|
BridgeableValue(40), BridgeableValue(50) ]
|
|
|
|
BridgeableValue.resetStats()
|
|
_ = test.call(array as NSArray)
|
|
|
|
expectEqual(0, bridgeFromOperationCount)
|
|
expectEqual(0, bridgeToOperationCount)
|
|
}
|
|
|
|
//===--- Non-bridging -----------------------------------------------------===//
|
|
// X is not bridged to Objective-C
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
struct X {}
|
|
|
|
tests.test("testMutableArray") {
|
|
let m = NSMutableArray(array: ["fu", "bar", "buzz"])
|
|
let a = m as NSArray as! [NSString]
|
|
|
|
// Create distinct array storage with a copy of the elements in a
|
|
let aCopy = Array(a)
|
|
|
|
// Modify the original mutable array
|
|
m.add("goop")
|
|
|
|
// Check that our [NSString] is unaffected
|
|
expectEqualSequence(aCopy, a)
|
|
}
|
|
|
|
tests.test("rdar://problem/27905230") {
|
|
// Casting an NSArray to Array<Any> would trap because of an erroneous
|
|
// precondition.
|
|
let dict = RDar27905230.mutableDictionaryOfMutableLists()!
|
|
let arr = dict["list"]!
|
|
expectEqual(arr[0] as! NSNull, NSNull())
|
|
expectEqual(arr[1] as! String, "")
|
|
expectEqual(arr[2] as! Int, 1)
|
|
expectEqual(arr[3] as! Bool, true)
|
|
expectEqual((arr[4] as! NSValue).rangeValue.location, 0)
|
|
expectEqual((arr[4] as! NSValue).rangeValue.length, 1)
|
|
expectEqual(arr[5] as! Date, Date(timeIntervalSince1970: 0))
|
|
}
|
|
|
|
tests.test("verbatimBridged/Base/withUnsafeBufferPointer") {
|
|
// https://github.com/apple/swift/issues/57014
|
|
// This tests a bad precondition that was fixed in
|
|
// https://github.com/apple/swift/pull/37960
|
|
guard #available(SwiftStdlib 5.5, *) else { return }
|
|
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
|
|
let b = a as! [Base]
|
|
let success: Bool = b.withUnsafeBufferPointer { buffer in
|
|
expectEqual(buffer.count, 4)
|
|
guard buffer.count == 4 else { return false }
|
|
expectEqual(buffer[0].value, 0)
|
|
expectEqual(buffer[1].value, 1)
|
|
expectEqual(buffer[2].value, 2)
|
|
expectEqual(buffer[3].value, 3)
|
|
return true
|
|
}
|
|
expectTrue(success)
|
|
}
|
|
|
|
tests.test("verbatimBridged/AnyObject/withUnsafeBufferPointer") {
|
|
// https://github.com/apple/swift/issues/57014
|
|
// This tests a bad precondition that was fixed in
|
|
// https://github.com/apple/swift/pull/37960
|
|
guard #available(SwiftStdlib 5.5, *) else { return }
|
|
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
|
|
let b = a as [AnyObject]
|
|
let success: Bool = b.withUnsafeBufferPointer { buffer in
|
|
expectEqual(buffer.count, 4)
|
|
guard buffer.count == 4 else { return false }
|
|
expectEqual((buffer[0] as? Base)?.value, 0)
|
|
expectEqual((buffer[1] as? Base)?.value, 1)
|
|
expectEqual((buffer[2] as? Base)?.value, 2)
|
|
expectEqual((buffer[3] as? Base)?.value, 3)
|
|
return true
|
|
}
|
|
expectTrue(success)
|
|
}
|
|
|
|
tests.test("verbatimBridged/Base/withUnsafeMutableBufferPointer") {
|
|
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
|
|
var b = a as! [Base]
|
|
let success: Bool = b.withUnsafeMutableBufferPointer { buffer in
|
|
expectEqual(buffer.count, 4)
|
|
guard buffer.count == 4 else { return false }
|
|
expectEqual(buffer[0].value, 0)
|
|
expectEqual(buffer[1].value, 1)
|
|
expectEqual(buffer[2].value, 2)
|
|
expectEqual(buffer[3].value, 3)
|
|
buffer[0] = Base(4)
|
|
return true
|
|
}
|
|
expectTrue(success)
|
|
expectEqual(b[0].value, 4)
|
|
}
|
|
|
|
tests.test("verbatimBridged/AnyObject/withUnsafeMutableBufferPointer") {
|
|
let a = NSArray(array: [Base(0), Base(1), Base(2), Base(3)])
|
|
var b = a as [AnyObject]
|
|
let success: Bool = b.withUnsafeMutableBufferPointer { buffer in
|
|
expectEqual(buffer.count, 4)
|
|
guard buffer.count == 4 else { return false }
|
|
expectEqual((buffer[0] as? Base)?.value, 0)
|
|
expectEqual((buffer[1] as? Base)?.value, 1)
|
|
expectEqual((buffer[2] as? Base)?.value, 2)
|
|
expectEqual((buffer[3] as? Base)?.value, 3)
|
|
buffer[0] = Base(4)
|
|
return true
|
|
}
|
|
expectTrue(success)
|
|
expectEqual((b[0] as? Base)?.value, 4)
|
|
}
|
|
|
|
runAllTests()
|