%# -*- mode: swift -*- //===--- NewArray.swift.gyb -----------------------------------*- swift -*-===// // // 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/NewArray.swift // RUN: %line-directive %t/NewArray.swift -- %target-build-swift %t/NewArray.swift -o %t/a.out -Xfrontend -disable-access-control // RUN: %target-codesign %t/a.out // RUN: %target-run %t/a.out 2>&1 | %line-directive %t/NewArray.swift -- %FileCheck %t/NewArray.swift --check-prefix=CHECK --check-prefix=CHECK-%target-runtime // REQUIRES: executable_test // REQUIRES: reflection import StdlibUnittest import StdlibCollectionUnittest //===----------------------------------------------------------------------===// func printSequence(_ x: T) { print("[", terminator: "") var prefix = "" for a in x { print(prefix, terminator: "") print(a, terminator: "") prefix = ", " } print("]") } typealias _BufferID = UnsafeRawPointer? protocol MyArrayProtocol : RandomAccessCollection, RangeReplaceableCollection, MutableCollection, ExpressibleByArrayLiteral where ArrayLiteralElement == Element { var _owner: AnyObject? { get } var capacity: Int { get } static func += < S : Sequence >(lhs: inout Self, rhs: S) where S.Iterator.Element == Iterator.Element } extension MyArrayProtocol { var _bufferID: _BufferID { return unsafeBitCast(_owner, to: _BufferID.self) } } extension Array : MyArrayProtocol {} extension ArraySlice : MyArrayProtocol {} extension ContiguousArray : MyArrayProtocol {} func checkReallocation( _ x: T, _ lastBuffer: _BufferID, _ reallocationExpected: Bool ) -> _BufferID { let currentBuffer = x._bufferID if (currentBuffer != lastBuffer) != reallocationExpected { let message = reallocationExpected ? "lack of" : "" print("unexpected \(message) reallocation") } return currentBuffer } func checkEqual< S1 : Sequence, S2 : Sequence >(_ a1: S1, _ a2: S2, _ expected: Bool) where S1.Iterator.Element == S2.Iterator.Element, S1.Iterator.Element : Equatable { if a1.elementsEqual(a2) != expected { let un = expected ? "un" : "" print("unexpectedly \(un)equal sequences!") } } func test< T : MyArrayProtocol >(_: T.Type, _ label: String) where T.Element == LifetimeTracked, T.Index == Int { print("test: \(label)...", terminator: "") var x: T = [ LifetimeTracked(1), LifetimeTracked(2), LifetimeTracked(3), LifetimeTracked(4), LifetimeTracked(5) ] checkEqual(x, LifetimeTracked(1)...LifetimeTracked(5), true) x.reserveCapacity(x.count + 2) checkEqual(x, LifetimeTracked(1)...LifetimeTracked(5), true) let bufferId0 = x._bufferID // Append a range of integers x += LifetimeTracked(0).. () ) { var a = a var reallocations = 0 // Note: right now this test is dependent on a growth factor of 2. // It's possible that factor will change, but (cursory) testing // has shown that using 1.5, the other popular growth factor, // slows things down. for _ in a.count..<(a.capacity * 4) { let oldId = a._bufferID growBy1(&a) if oldId != a._bufferID { reallocations += 1 } } if reallocations > 3 { print( "Unexpectedly found \(reallocations) reallocations " + "of \(label) when growing via \(growthDescription)") } } checkReallocations(x, "append") { (x: inout T) -> () in x.append(LifetimeTracked(42)) } checkReallocations(x, "+=") { (x: inout T) -> () in x.append(LifetimeTracked(42)) } print("done.") } print("testing...") // CHECK: testing... test(ContiguousArray.self, "ContiguousArray") // CHECK-NEXT: test: ContiguousArray...done test(Array.self, "Array") // CHECK-NEXT: test: Array...done test(ArraySlice.self, "ArraySlice") // CHECK-NEXT: test: ArraySlice...done func testAsArray() { print("== AsArray ==") var w: ContiguousArray = [LifetimeTracked(4), LifetimeTracked(2), LifetimeTracked(1)] // CHECK: == AsArray == let x = ContiguousArray(w) print(w._bufferID == x._bufferID) // CHECK-NEXT: true let y = Array(x) print(x._bufferID == y._bufferID) // CHECK-NEXT: true // Because of their indirection, arrays of classes can share // buffers. let y1 = Array(y) print(y1._bufferID == y._bufferID) // CHECK-NEXT: true let z = ArraySlice(y) print(y._bufferID == z._bufferID) // CHECK-NEXT: true w = ContiguousArray(z) print(w._bufferID == z._bufferID) // CHECK-NEXT: true let zz = y[0..<2] print(zz._bufferID!) // CHECK-NEXT: 0x } testAsArray() #if _runtime(_ObjC) import Foundation func nsArrayOfStrings() -> NSArray { let src: ContiguousArray = ["foo", "bar", "baz"] return src.withUnsafeBufferPointer { return NSArray( objects: unsafeBitCast($0.baseAddress!, to: UnsafeMutablePointer.self), count: $0.count) } } func swiftArrayWithNSArrayOfStrings() -> Array { return nsArrayOfStrings() as! [NSString] } func testCocoa() { print("== Cocoa ==") // CHECK-objc: == Cocoa == var a = swiftArrayWithNSArrayOfStrings() printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz] a.append("qux") printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz, qux] a = swiftArrayWithNSArrayOfStrings() printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz] let b = a a[1] = "garply" printSequence(a) // CHECK-objc-NEXT: [foo, garply, baz] // Mutating an element in a has no effect on b printSequence(b) // CHECK-objc-NEXT: [foo, bar, baz] a = swiftArrayWithNSArrayOfStrings() a.insert("bag", at: 2) printSequence(a) // CHECK-objc-NEXT: [foo, bar, bag, baz] a = swiftArrayWithNSArrayOfStrings() a.reserveCapacity(30) printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz] print(a.capacity >= 30) // CHECK-objc-NEXT: true // Prove that we create contiguous storage for an opaque NSArray a.withUnsafeBufferPointer { (p) -> () in print(p[0]) // CHECK-objc-NEXT: foo } } testCocoa() #endif // _runtime(_ObjC) func testSlice() { print("== ArraySlice ==") // CHECK: == ArraySlice == // do some tests on the shared semantics var b = ContiguousArray(LifetimeTracked(0)..") // CHECK-NEXT: <5> print("bSlice0: \(bSlice)") // CHECK-NEXT: bSlice0: [3, 4, 5, 6, 7] // bSlice += LifetimeTracked(11)..(_buffer: _ArrayBuffer(nsArray: nsArrayOfStrings())) printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz] var aSlice = a[1..<3] // CHECK-objc-NEXT: [bar, baz] printSequence(aSlice) // Writing into aSlice works aSlice[1] = "buzz" // CHECK-objc-NEXT: [buzz, baz] printSequence(aSlice) // ...and doesn't affect a printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz] // Appending to aSlice works... aSlice.append("fodder") print("<\(aSlice.count)>") // CHECK-objc-NEXT: <3> printSequence(aSlice) // CHECK-objc-NEXT: [buzz, baz, fodder] // And doesn't change a printSequence(a) // CHECK-objc-NEXT: [foo, bar, baz] #endif } testSlice() //===--- sub-range replacement --------------------------------------------===// // Size of the array on which we're going to test "replace." // testing time grows roughly as the cube of this constant let testWidth = 11 %arrayTypes = ['ContiguousArray', 'Array', 'ArraySlice'] %for A in arrayTypes: func testReplace(_ make: @escaping () -> ${A}) { checkRangeReplaceable( make, { LifetimeTracked(100).. ${A} = { var x = ${A}() // make sure some - but not all - replacements will have to grow the buffer x.reserveCapacity(testWidth * 3 / 2) x += LifetimeTracked(0)..().isEmpty) // CHECK-NEXT: true print(${A}(42...42).isEmpty) // CHECK-NEXT: false print("<\(${A}(3..<43).first!)>") // CHECK-NEXT: <3> print("<\(${A}(3..<43).last!)>") // CHECK-NEXT: <42> print("<\(String(describing: ${A}().first))>") // CHECK-NEXT: nil print("<\(String(describing:${A}().last))>") // CHECK-NEXT: nil var a = ${A}(LifetimeTracked(0)..