//===--- 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 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()