mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
548 lines
15 KiB
Swift
548 lines
15 KiB
Swift
%# -*- mode: swift -*-
|
|
//===--- Array.swift ------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
// RUN-DISABLED: %target-run-simple-swift | FileCheck %s
|
|
// RUN: rm -rf %t && mkdir -p %t && %S/../../utils/gyb %s -o %t/NewArray.swift
|
|
// RUN: %S/../../utils/line-directive %t/NewArray.swift -- %target-build-swift -module-cache-path %t/clang-module-cache %t/NewArray.swift -o %t/a.out -Xfrontend -disable-access-control
|
|
// RUN: %target-run %t/a.out | %S/../../utils/line-directive %t/NewArray.swift -- FileCheck %t/NewArray.swift
|
|
|
|
var xCount = 0
|
|
var xSerial = 0
|
|
|
|
// Instead of testing with Int elements, we use this wrapper class
|
|
// that can help us track allocations and find issues with object
|
|
// lifetime inside Array implementations.
|
|
class X : ForwardIndexType, Comparable, Printable, IntegerLiteralConvertible {
|
|
init(_ value: Int) {
|
|
++xCount
|
|
serial = ++xSerial
|
|
self.value = value
|
|
}
|
|
|
|
deinit {
|
|
assert(serial > 0, "double destruction!")
|
|
--xCount
|
|
serial = -serial
|
|
}
|
|
|
|
var description: String {
|
|
assert(serial > 0, "dead X!")
|
|
return value.description
|
|
}
|
|
|
|
func successor() -> X {
|
|
return X(self.value.successor())
|
|
}
|
|
|
|
class func convertFromIntegerLiteral(value: Int) -> X {
|
|
return X(value)
|
|
}
|
|
|
|
var value: Int
|
|
var serial: Int
|
|
}
|
|
|
|
func == (x: X, y: X) -> Bool {
|
|
return x.value == y.value
|
|
}
|
|
|
|
func < (x: X, y: X) -> Bool {
|
|
return x.value < y.value
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
func printSequence<T: SequenceType>(x: T) {
|
|
print("[")
|
|
var prefix = ""
|
|
for a in x {
|
|
print(prefix)
|
|
print(a)
|
|
prefix = ", "
|
|
}
|
|
println("]")
|
|
}
|
|
|
|
func bufferID<T : ArrayType>(x: T) -> Int {
|
|
return reinterpretCast(x._buffer.baseAddress) as Int
|
|
}
|
|
|
|
func checkReallocation<T : ArrayType>(
|
|
x: T, lastBuffer: Int, reallocationExpected: Bool
|
|
) -> Int {
|
|
let currentBuffer = bufferID(x)
|
|
if (currentBuffer != lastBuffer) != reallocationExpected {
|
|
let message = reallocationExpected ? "lack of" : ""
|
|
println("unexpected \(message) reallocation")
|
|
}
|
|
return currentBuffer
|
|
}
|
|
|
|
func checkEqual<
|
|
S1 : SequenceType, S2 : SequenceType
|
|
where
|
|
S1.Generator.Element == S2.Generator.Element,
|
|
S1.Generator.Element : Equatable
|
|
>(a1: S1, a2: S2, expected: Bool) {
|
|
if equal(a1, a2) != expected {
|
|
let un = expected ? "un" : ""
|
|
println("unexpectedly \(un)equal sequences!")
|
|
}
|
|
}
|
|
|
|
func test<
|
|
T: ArrayType
|
|
where T.Generator.Element == T._Buffer.Element,
|
|
T._Buffer.Element == T.Element,
|
|
T.Element == X,
|
|
T.Index == Int
|
|
>(_: T.Type, label: String) {
|
|
print("test: \(label)...")
|
|
|
|
var x: T = [1, 2, 3, 4, 5]
|
|
|
|
checkEqual(x, 1...5, true)
|
|
|
|
x.reserveCapacity(x.count + 2)
|
|
checkEqual(x, 1...5, true)
|
|
|
|
let bufferId0 = bufferID(x)
|
|
|
|
// Append a range of integers
|
|
x += 0..<2
|
|
let bufferId1 = checkReallocation(x, bufferId0, false)
|
|
|
|
for i in x.count..<(x.capacity + 1) {
|
|
let bufferId1a = checkReallocation(x, bufferId1, false)
|
|
x.append(13)
|
|
}
|
|
let bufferId2 = checkReallocation(x, bufferId1, true)
|
|
|
|
let y = x
|
|
x[x.endIndex.predecessor()] = 17
|
|
let bufferId3 = checkReallocation(x, bufferId2, true)
|
|
checkEqual(x, y, false)
|
|
|
|
func checkReallocations(
|
|
var a: T, growthDescription: String, growBy1: (inout _: T)->()
|
|
) -> () {
|
|
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 = bufferID(a)
|
|
growBy1(&a)
|
|
if oldId != bufferID(a) {
|
|
++reallocations
|
|
}
|
|
}
|
|
|
|
if reallocations > 3 {
|
|
println(
|
|
"Unexpectedly found \(reallocations) reallocations "
|
|
+ "of \(label) when growing via \(growthDescription)")
|
|
}
|
|
}
|
|
|
|
checkReallocations(x, "append") { (inout x: T)->() in x.append(42) }
|
|
checkReallocations(x, "+=") { (inout x: T)->() in x.append(42) }
|
|
println("done.")
|
|
}
|
|
|
|
println("testing...")
|
|
// CHECK: testing...
|
|
|
|
test(ContiguousArray<X>.self, "ContiguousArray")
|
|
// CHECK-NEXT: test: ContiguousArray...done
|
|
|
|
test(Array<X>.self, "Array")
|
|
// CHECK-NEXT: test: Array...done
|
|
|
|
test(Slice<X>.self, "Slice")
|
|
// CHECK-NEXT: test: Slice...done
|
|
|
|
func testAsArray() {
|
|
println("== AsArray ==")
|
|
var w: ContiguousArray<X> = [4, 2, 1]
|
|
// CHECK: == AsArray ==
|
|
|
|
let x = ContiguousArray(w)
|
|
println(bufferID(w) == bufferID(x))
|
|
// CHECK-NEXT: false
|
|
|
|
let y = Array(x)
|
|
println(bufferID(x) == bufferID(y))
|
|
// CHECK-NEXT: false
|
|
|
|
// Because of their indirection, arrays of classes can share
|
|
// buffers.
|
|
let y1 = Array(y)
|
|
println(bufferID(y1) == bufferID(y))
|
|
// However, I'm not implementing that optimization yet, thus false.
|
|
// CHECK-NEXT: false
|
|
|
|
let z = Slice(y)
|
|
println(bufferID(y) == bufferID(z))
|
|
// CHECK-NEXT: false
|
|
|
|
w = ContiguousArray(z)
|
|
println(bufferID(w) == bufferID(z))
|
|
// CHECK-NEXT: false
|
|
}
|
|
testAsArray()
|
|
|
|
import Foundation
|
|
|
|
func nsArrayOfStrings() -> Array<NSString> {
|
|
let src: ContiguousArray<NSString> = ["foo", "bar", "baz"]
|
|
|
|
return src.withUnsafePointerToElements {
|
|
let ns = NSArray(objects: UnsafePointer($0), count: src.count)
|
|
return Array(_ArrayBuffer(reinterpretCast(ns) as _CocoaArrayType))
|
|
}
|
|
}
|
|
|
|
func testCocoa() {
|
|
println("== Cocoa ==")
|
|
// CHECK: == Cocoa ==
|
|
|
|
var a = nsArrayOfStrings()
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, baz]
|
|
|
|
a.append("qux")
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, baz, qux]
|
|
|
|
a = nsArrayOfStrings()
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, baz]
|
|
|
|
var b = a
|
|
|
|
a[1] = "garply"
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, garply, baz]
|
|
|
|
// Mutating an element in a has no effect on b
|
|
printSequence(b)
|
|
// CHECK-NEXT: [foo, bar, baz]
|
|
|
|
a = nsArrayOfStrings()
|
|
a.insert("bag", atIndex:2)
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, bag, baz]
|
|
|
|
a = nsArrayOfStrings()
|
|
a.reserveCapacity(30)
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, baz]
|
|
|
|
println(a.capacity >= 30)
|
|
// CHECK-NEXT: true
|
|
|
|
// Prove that we create contiguous storage for an opaque NSArray
|
|
a.withUnsafePointerToElements {
|
|
(p)->() in
|
|
println(p.memory)
|
|
// CHECK-NEXT: foo
|
|
}
|
|
}
|
|
testCocoa()
|
|
|
|
extension Slice {
|
|
mutating func qsort(compare: (T,T)->Bool) {
|
|
_quickSort(&self, indices(self), compare)
|
|
}
|
|
}
|
|
|
|
func testSlice() {
|
|
println("== Slice ==")
|
|
// CHECK: == Slice ==
|
|
|
|
// do some tests on the shared semantics
|
|
var b = ContiguousArray(X(0)..<X(7))
|
|
|
|
// Slice it
|
|
var bSlice = b[3..<5]
|
|
println("<\(bSlice.count)>")
|
|
// CHECK-NEXT: <2>
|
|
println("bSlice0: \(bSlice)") // CHECK-NEXT: bSlice0: [3, 4]
|
|
|
|
// bSlice += X(11)..<X(13)
|
|
|
|
// Writing into b does not change bSlice
|
|
b[4] = 41
|
|
println("bSlice1: \(bSlice)") // CHECK-NEXT: bSlice1: [3, 4]
|
|
|
|
// Writing into bSlice does not change b
|
|
bSlice[1] = 42
|
|
|
|
|
|
println("bSlice2: \(bSlice)") // CHECK-NEXT: bSlice2: [3, 42]
|
|
printSequence(b) // CHECK-NEXT: [0, 1, 2, 3, 41, 5, 6]
|
|
|
|
var c = b
|
|
b[4..<b.count].qsort(<)
|
|
printSequence(b) // CHECK-NEXT: [0, 1, 2, 3, 5, 6, 41]
|
|
printSequence(c) // CHECK-NEXT: [0, 1, 2, 3, 41, 5, 6]
|
|
|
|
// Now a bridged slice
|
|
var a = Array<NSString>(
|
|
_ArrayBuffer(nsArrayOfStrings()._asCocoaArray()))
|
|
|
|
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
|
|
|
|
var aSlice = a[1..<3] // CHECK-NEXT: [bar, baz]
|
|
printSequence(aSlice)
|
|
|
|
// Writing into aSlice works
|
|
aSlice[0] = "buzz" // CHECK-NEXT: [buzz, baz]
|
|
printSequence(aSlice)
|
|
|
|
// ...and doesn't affect a
|
|
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
|
|
|
|
// Appending to aSlice works...
|
|
aSlice.append("fodder")
|
|
println("<\(aSlice.count)>") // CHECK-NEXT: <3>
|
|
printSequence(aSlice) // CHECK-NEXT: [buzz, baz, fodder]
|
|
|
|
// And doesn't change a
|
|
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
|
|
}
|
|
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
|
|
|
|
%for A in ['ContiguousArray', 'Array', 'Slice']:
|
|
|
|
func testReplace(make: ()->${A}<X>) {
|
|
|
|
typealias A = ${A}<X>
|
|
|
|
// First make an independent copy of the array that we can use for
|
|
// comparison later.
|
|
var source = ContiguousArray<A.Generator.Element>()
|
|
for x in make() {
|
|
source.append(x)
|
|
}
|
|
|
|
for i in indices(source) {
|
|
for j in i..<source.count {
|
|
|
|
let oldCount = j - i
|
|
for newCount in 0..<(2 * oldCount) {
|
|
let newValues = X(100)..<X(100 + newCount)
|
|
|
|
func reportFailure(inout a: A, message: String) {
|
|
print("\(message) when replacing indices \(i)...\(j) in ")
|
|
printSequence(source)
|
|
print(" with ")
|
|
printSequence(newValues)
|
|
print(" yielding ")
|
|
printSequence(a)
|
|
println("====================================")
|
|
}
|
|
|
|
var a = make()
|
|
|
|
a.replaceRange(i..<j, with: newValues)
|
|
let growth = newCount - oldCount
|
|
|
|
let expectedCount = source.count + growth
|
|
if a.count != expectedCount {
|
|
reportFailure(
|
|
&a, "\(a.count) != expected count \(expectedCount)")
|
|
}
|
|
|
|
for k in 0..<a.count {
|
|
let expectedValue = k < i ? source[k].value
|
|
: k < j + growth ? 100 + k - i
|
|
: source[k - growth].value
|
|
|
|
if a[k].value != expectedValue {
|
|
reportFailure(
|
|
&a, "a[\(k)] = \(a[k].value) != expected value \(expectedValue)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func testReplace${A}(
|
|
makeOne: ()->${A}<X> = {
|
|
var x = ${A}<X>()
|
|
// make sure some - but not all - replacements will have to grow the buffer
|
|
x.reserveCapacity(testWidth * 3 / 2)
|
|
x += X(0)..<X(testWidth)
|
|
return x
|
|
}
|
|
) {
|
|
testReplace(makeOne)
|
|
|
|
// Create one that will not be uniquely-referenced so we can test
|
|
// the out-of-place code paths.
|
|
let r = makeOne()
|
|
testReplace({ r })
|
|
|
|
// This test should ensure r's retain isn't dropped before we start testing.
|
|
if (r.count != testWidth) {
|
|
println("something bad happened!")
|
|
}
|
|
}
|
|
|
|
println("testing subrange replacement in ${A}")
|
|
testReplace${A}()
|
|
%end
|
|
|
|
// Also test with a sub-slice of some larger buffer. The "trailing"
|
|
// case is interesting because when the buffer is uniquely referenced
|
|
// we can expect to expand the slice in-place
|
|
for (maxValue, label) in [(testWidth, "trailing"), (testWidth*2, "interior")] {
|
|
println("testing subrange replacement in \(label) Sub-Slice")
|
|
testReplaceSlice {
|
|
var a = ContiguousArray(X(-testWidth)..<X(maxValue))
|
|
a.reserveCapacity(a.count * 3 / 2)
|
|
return a[testWidth..<(2 * testWidth)]
|
|
}
|
|
}
|
|
|
|
// CHECK-NEXT: testing subrange replacement in ContiguousArray
|
|
// CHECK-NEXT: testing subrange replacement in Array
|
|
// CHECK-NEXT: testing subrange replacement in Slice
|
|
// CHECK-NEXT: testing subrange replacement in trailing Sub-Slice
|
|
// CHECK-NEXT: testing subrange replacement in interior Sub-Slice
|
|
|
|
//===--- inout violations -------------------------------------------------===//
|
|
|
|
// The user has to obey certain rules when things are passed via
|
|
// inout, but in those cases we only guarantee memory-safety, not
|
|
// coherent semantics. Let's try to force a memory-safety problem
|
|
// here. This crashes when withUnsafeMutableBufferPointer is not
|
|
// sufficiently careful.
|
|
func testInoutViolation() {
|
|
var a: [X] = [
|
|
X(10), X(8), X(6), X(4), X(2), X(0), X(9), X(7), X(5), X(3), X(1)
|
|
]
|
|
|
|
%for A in ['ContiguousArray', 'Array', 'Slice']:
|
|
if true {
|
|
var b = ${A}(a)
|
|
b.sort { x, y in
|
|
b.removeAll()
|
|
return x < y
|
|
}
|
|
}
|
|
%end
|
|
|
|
// An overload of sort for Arrays uses withUnsafeMutableBufferPointer,
|
|
// which disables bounds checks.
|
|
sort(&a) { x, y in
|
|
a = [] // Invalidate the whole array during sorting
|
|
return x < y
|
|
}
|
|
}
|
|
testInoutViolation()
|
|
|
|
//===--- single-element modifiers -----------------------------------------===//
|
|
|
|
%for A in ['ContiguousArray', 'Array', 'Slice']:
|
|
|
|
func testSingleElementModifiers${A}() {
|
|
println("testing ${A} single-argument modifiers")
|
|
// CHECK-NEXT: testing ${A} single-argument modifiers
|
|
|
|
var a = ${A}(X(0)..<10)
|
|
println(a.removeLast().value) // CHECK-NEXT: 9
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
|
|
|
a.insert(42, atIndex: 4)
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 42, 4, 5, 6, 7, 8]
|
|
|
|
println(a.removeAtIndex(2).value) // CHECK-NEXT: 2
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 3, 42, 4, 5, 6, 7, 8]
|
|
}
|
|
testSingleElementModifiers${A}()
|
|
%end
|
|
|
|
//===--- isEmpty, first, last ---------------------------------------------===//
|
|
|
|
%for A in ['ContiguousArray', 'Array', 'Slice']:
|
|
|
|
func testIsEmptyFirstLast${A}() {
|
|
println("testing ${A} isEmpty, first, and last")
|
|
// CHECK-NEXT: testing ${A} isEmpty, first, and last
|
|
|
|
println(${A}<Int>().isEmpty) // CHECK-NEXT: true
|
|
println(${A}(42...42).isEmpty) // CHECK-NEXT: false
|
|
|
|
println("<\(${A}(3...42).first!)>") // CHECK-NEXT: <3>
|
|
println("<\(${A}(3...42).last!)>") // CHECK-NEXT: <42>
|
|
|
|
println("<\(${A}<Int>().first)>") // CHECK-NEXT: nil
|
|
println("<\(${A}<Int>().last)>") // CHECK-NEXT: nil
|
|
|
|
var a = ${A}(X(0)..<10)
|
|
println(a.removeLast().value) // CHECK-NEXT: 9
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
|
|
|
a.insert(42, atIndex: 4)
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 42, 4, 5, 6, 7, 8]
|
|
|
|
println(a.removeAtIndex(2).value) // CHECK-NEXT: 2
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 3, 42, 4, 5, 6, 7, 8]
|
|
}
|
|
testIsEmptyFirstLast${A}()
|
|
%end
|
|
|
|
//===--- Regression Tests -------------------------------------------------===//
|
|
func rdar16958865() {
|
|
var a: [Int] = []
|
|
a += SequenceOf([ 42, 4242 ])
|
|
// CHECK-NEXT: [42, 4242]
|
|
println(a)
|
|
}
|
|
rdar16958865()
|
|
|
|
import SpriteKit
|
|
|
|
class Rdar16914909 : NSObject {
|
|
var basicColorSet = [SKColor]()
|
|
|
|
func doColorStuff() {
|
|
basicColorSet.append(SKColor.lightGrayColor())
|
|
println("appended")
|
|
}
|
|
}
|
|
|
|
Rdar16914909().doColorStuff()
|
|
// CHECK-NEXT: appended
|
|
|
|
println("leaks = \(xCount)")
|
|
// CHECK-NEXT: leaks = 0
|
|
|
|
// CHECK-NEXT: all done.
|
|
println("all done.")
|
|
|
|
// ${'Local Variables'}:
|
|
// eval: (read-only-mode 1)
|
|
// End:
|