blob: 3879c9f1a97d497cf6c4f1afc77fdf7178918bae [file] [log] [blame]
//===--- ArrayBridge.swift - Tests of Array casting and bridging ----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
// Requires swift-version 4
// UNSUPPORTED: swift_test_mode_optimize_none_with_implicit_dynamic
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("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") {
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))
}
runAllTests()