[stdlib] API additions for basic noncopyable primitives

- Add `exchange(_:with:)`
- Add `Optional.take()`
- Add `Unsafe[Mutable]BufferPointer.extracting(_:)`
- Finish generalizing `withExtendedLifetime`
- Radically simplify the implementation of `swap(_:_:)`
This commit is contained in:
Karoy Lorentey
2024-05-21 16:22:03 -07:00
parent c8b5344d3e
commit ab51f1630c
11 changed files with 489 additions and 42 deletions

View File

@@ -20,10 +20,10 @@
/// return value for the `withExtendedLifetime(_:_:)` method.
/// - Returns: The return value, if any, of the `body` closure parameter.
@_alwaysEmitIntoClient
public func withExtendedLifetime<T: ~Copyable, Result: ~Copyable>(
public func withExtendedLifetime<T: ~Copyable, E: Error, Result: ~Copyable>(
_ x: borrowing T,
_ body: () throws -> Result // FIXME: Typed throws rdar://126576356
) rethrows -> Result {
_ body: () throws(E) -> Result
) throws(E) -> Result {
defer { _fixLifetime(x) }
return try body()
}
@@ -32,8 +32,7 @@ public func withExtendedLifetime<T: ~Copyable, Result: ~Copyable>(
@_silgen_name("$ss20withExtendedLifetimeyq_x_q_yKXEtKr0_lF")
@usableFromInline
internal func __abi_withExtendedLifetime<T, Result>(
_ x: T,
_ body: () throws -> Result // FIXME: Typed throws rdar://126576356
_ x: T, _ body: () throws -> Result
) rethrows -> Result {
defer { _fixLifetime(x) }
return try body()
@@ -49,9 +48,10 @@ internal func __abi_withExtendedLifetime<T, Result>(
/// return value for the `withExtendedLifetime(_:_:)` method.
/// - Returns: The return value, if any, of the `body` closure parameter.
@_alwaysEmitIntoClient
public func withExtendedLifetime<T, Result: ~Copyable>(
_ x: T, _ body: (T) throws -> Result // FIXME: Typed throws rdar://126576356
) rethrows -> Result {
public func withExtendedLifetime<T: ~Copyable, E: Error, Result: ~Copyable>(
_ x: borrowing T,
_ body: (borrowing T) throws(E) -> Result
) throws(E) -> Result {
defer { _fixLifetime(x) }
return try body(x)
}
@@ -60,7 +60,7 @@ public func withExtendedLifetime<T, Result: ~Copyable>(
@_silgen_name("$ss20withExtendedLifetimeyq_x_q_xKXEtKr0_lF")
@usableFromInline
internal func __abi_withExtendedLifetime<T, Result>(
_ x: T, _ body: (T) throws -> Result // FIXME: Typed throws rdar://126576356
_ x: T, _ body: (T) throws -> Result
) rethrows -> Result {
defer { _fixLifetime(x) }
return try body(x)

View File

@@ -529,24 +529,24 @@ extension MutableCollection {
@inlinable
@_preInverseGenerics
public func swap<T: ~Copyable>(_ a: inout T, _ b: inout T) {
// Semantically equivalent to (a, b) = (b, a).
// Microoptimized to avoid retain/release traffic.
#if $BuiltinUnprotectedAddressOf
let p1 = Builtin.unprotectedAddressOf(&a)
let p2 = Builtin.unprotectedAddressOf(&b)
#else
let p1 = Builtin.addressof(&a)
let p2 = Builtin.addressof(&b)
#endif
_debugPrecondition(
p1 != p2,
"swapping a location with itself is not supported")
// Take from P1.
let tmp: T = Builtin.take(p1)
// Transfer P2 into P1.
Builtin.initialize(Builtin.take(p2) as T, p1)
// Initialize P2.
Builtin.initialize(tmp, p2)
let temp = consume a
a = consume b
b = consume temp
}
/// Replaces the value of a mutable value with the supplied new value,
/// returning the original.
///
/// - Parameters:
/// - item: A mutable binding.
/// - newValue: The new value of `item`.
/// - Returns: The original value of `item`.
@_alwaysEmitIntoClient
public func exchange<T: ~Copyable>(
_ item: inout T,
with newValue: consuming T
) -> T {
let oldValue = consume item
item = consume newValue
return oldValue
}

View File

@@ -236,7 +236,7 @@ extension Unicode {
// If we have a leftover composee, make sure to return it.
// We may still have things in the buffer which are not complete segments.
return composee._take() ?? buffer.next()?.scalar
return composee.take() ?? buffer.next()?.scalar
}
}
}

View File

@@ -279,7 +279,7 @@ extension Unicode._NFDNormalizer {
// The buffer contains the decomposed segment *prior to*
// any pending starter we might have.
return buffer.next() ?? pendingStarter._take()
return buffer.next() ?? pendingStarter.take()
}
@inline(__always)
@@ -287,7 +287,7 @@ extension Unicode._NFDNormalizer {
_ nextFromSource: () -> Unicode.Scalar?
) -> ScalarAndNormData? {
if let pendingStarter = pendingStarter._take() {
if let pendingStarter = pendingStarter.take() {
return pendingStarter
} else if let nextScalar = nextFromSource() {
return (nextScalar, Unicode._NormData(nextScalar))

View File

@@ -232,7 +232,7 @@ extension Optional where Wrapped: ~Copyable {
) throws(E) -> U? {
#if compiler(>=6.0) && $NoncopyableGenerics
switch self {
case .some(_borrowing y):
case .some(let y):
return .some(try transform(y))
case .none:
return .none
@@ -310,7 +310,7 @@ extension Optional where Wrapped: ~Copyable {
) throws(E) -> U? {
#if compiler(>=6.0) && $NoncopyableGenerics
switch self {
case .some(_borrowing y):
case .some(let y):
return try transform(y)
case .none:
return .none
@@ -418,8 +418,8 @@ extension Optional where Wrapped: ~Copyable {
///
/// - Returns: The wrapped value being stored in this instance. If this
/// instance is `nil`, returns `nil`.
@_alwaysEmitIntoClient // FIXME(NCG): Make this public.
public mutating func _take() -> Self {
@_alwaysEmitIntoClient
public mutating func take() -> Self {
let result = consume self
self = nil
return result

View File

@@ -99,7 +99,7 @@ extension Result where Success: ~Copyable {
_ transform: (borrowing Success) -> NewSuccess
) -> Result<NewSuccess, Failure> {
switch self {
case .success(borrowing success):
case .success(let success):
return .success(transform(success))
case let .failure(failure):
return .failure(failure)
@@ -241,7 +241,7 @@ extension Result where Success: ~Copyable {
_ transform: (borrowing Success) -> Result<NewSuccess, Failure>
) -> Result<NewSuccess, Failure> {
switch self {
case .success(borrowing success):
case .success(let success):
return transform(success)
case let .failure(failure):
return .failure(failure)

View File

@@ -456,6 +456,79 @@ extension Unsafe${Mutable}BufferPointer where Element: ~Copyable {
}
extension Unsafe${Mutable}BufferPointer where Element: ~Copyable {
/// Constructs a standalone buffer pointer over the items within the supplied
/// range of positions in the memory region addressed by this buffer pointer.
///
/// The returned buffer's first item is always at index 0; unlike buffer
/// slices, extracted buffers do not generally share their indices with the
/// original buffer pointer.
///
/// withUnsafeTemporaryAllocation(of: Int.self, capacity: 5) { buffer in
/// buffer.initialize(repeating: 0)
/// // buffer contains [0, 0, 0, 0, 0]
/// let part = buffer.extracting(2 ..< 4)
/// part[0] = 1
/// part[1] = 2
/// // buffer now contains [0, 0, 1, 2, 0]
/// }
///
/// When `Element` is copyable, the `extracting` operation is equivalent to
/// slicing the buffer then rebasing the resulting buffer slice:
///
/// let a = buffer.extracting(i ..< j)
/// let b = UnsafeBufferPointer(rebasing: buffer[i ..< j])
/// // `a` and `b` are now holding the same buffer
///
/// However, unlike slicing, the `extracting` operation remains available even
/// if `Element` happens to be noncopyable.
///
/// - Parameter bounds: A valid range of indices within this buffer.
/// - Returns: A new buffer pointer over the items at `bounds`.
@_alwaysEmitIntoClient
public func extracting(_ bounds: Range<Int>) -> Self {
_precondition(bounds.lowerBound >= 0 && bounds.upperBound <= count,
"Index out of range")
guard let start = self.baseAddress else {
return Self(start: nil, count: 0)
}
return Self(start: start + bounds.lowerBound, count: bounds.count)
}
/// Constructs a standalone buffer pointer over the items within the supplied
/// range of positions in the memory region addressed by this buffer pointer.
///
/// The returned buffer's first item is always at index 0; unlike buffer
/// slices, extracted buffers do not generally share their indices with the
/// original buffer pointer.
///
/// withUnsafeTemporaryAllocation(of: Int.self, capacity: 5) { buffer in
/// buffer.initialize(repeating: 0)
/// // buffer contains [0, 0, 0, 0, 0]
/// let part = buffer.extracting(2...)
/// part[0] = 1
/// part[1] = 2
/// // buffer now contains [0, 0, 1, 2, 0]
/// }
///
/// When `Element` is copyable, the `extracting` operation is equivalent to
/// slicing the buffer then rebasing the resulting buffer slice:
///
/// let a = buffer.extracting(i ..< j)
/// let b = UnsafeBufferPointer(rebasing: buffer[i ..< j])
/// // `a` and `b` are now holding the same buffer
///
/// However, unlike slicing, the `extracting` operation remains available even
/// if `Element` happens to be noncopyable.
///
/// - Parameter bounds: A valid range of indices within this buffer.
/// - Returns: A new buffer pointer over the items at `bounds`.
@_alwaysEmitIntoClient
public func extracting(_ bounds: some RangeExpression<Int>) -> Self {
extracting(bounds.relative(to: Range(uncheckedBounds: (0, count))))
}
}
@_disallowFeatureSuppression(NoncopyableGenerics)
extension Unsafe${Mutable}BufferPointer {
/// Accesses the element at the specified position.

View File

@@ -13,6 +13,6 @@ struct Foo<T> {
// in an optional
let x: Foo<Bar>? = Foo<Bar>()
x.#^FOO_OPTIONAL_1^#
// FOO_OPTIONAL_1: Begin completions, 7 items
// FOO_OPTIONAL_1: Begin completions, 8 items
// FOO_OPTIONAL_1-DAG: Decl[InstanceMethod]/CurrNominal/Erase[1]: ?.myFunction({#(foobar): Bar#})[#Void#]; name=myFunction(:)
// FOO_OPTIONAL_1-DAG: Keyword[self]/CurrNominal: self[#Foo<Bar>?#]; name=self

View File

@@ -148,7 +148,7 @@ class C4 {
// UNRESOLVED_3-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#]; name=hash(:)
// Exhaustive to make sure we don't include `init({#(some):` or `init({#nilLiteral:` entries
// UNRESOLVED_3_OPT: Begin completions, 9 items
// UNRESOLVED_3_OPT: Begin completions, 10 items
// UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: North[#SomeEnum1#];
// UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: South[#SomeEnum1#];
// UNRESOLVED_3_OPT-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#];
@@ -160,7 +160,7 @@ class C4 {
// UNRESOLVED_3_OPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<SomeEnum1>#})[#(into: inout Hasher) -> Void#];
// Exhaustive to make sure we don't include `init({#(some):` or `init({#nilLiteral:` entries
// UNRESOLVED_3_OPTOPTOPT: Begin completions, 9 items
// UNRESOLVED_3_OPTOPTOPT: Begin completions, 10 items
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: North[#SomeEnum1#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: South[#SomeEnum1#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#];
@@ -169,6 +169,7 @@ class C4 {
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Convertible]: some({#SomeEnum1??#})[#Optional<SomeEnum1??>#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: map({#(self): Optional<SomeEnum1??>#})[#((SomeEnum1??) throws(Error) -> ~Copyable) -> ~Copyable?#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: flatMap({#(self): Optional<SomeEnum1??>#})[#((SomeEnum1??) throws(Error) -> ~Copyable?) -> ~Copyable?#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: take({#(self): &Optional<SomeEnum1??>#})[#() -> Optional<SomeEnum1??>#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<SomeEnum1??>#})[#(into: inout Hasher) -> Void#];
enum Somewhere {
@@ -180,7 +181,7 @@ extension Optional where Wrapped == Somewhere {
}
func testOptionalWithCustomExtension() {
var _: Somewhere? = .#^UNRESOLVED_OPT_4^#
// UNRESOLVED_OPT_4: Begin completions, 11 items
// UNRESOLVED_OPT_4: Begin completions, 12 items
// UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: earth[#Somewhere#];
// UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: mars[#Somewhere#];
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): Somewhere#})[#(into: inout Hasher) -> Void#];
@@ -191,6 +192,7 @@ func testOptionalWithCustomExtension() {
// UNRESOLVED_OPT_4-DAG: Decl[StaticVar]/CurrNominal/TypeRelation[Convertible]: nowhere[#Optional<Somewhere>#]; name=nowhere
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: map({#(self): Optional<Somewhere>#})[#((Somewhere) throws(Error) -> ~Copyable) -> ~Copyable?#];
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: flatMap({#(self): Optional<Somewhere>#})[#((Somewhere) throws(Error) -> ~Copyable?) -> ~Copyable?#];
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: take({#(self): &Optional<Somewhere>#})[#() -> Optional<Somewhere>#];
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<Somewhere>#})[#(into: inout Hasher) -> Void#];
// UNRESOLVED_OPT_4-NOT: init({#(some):
// UNRESOLVED_OPT_4-NOT: init({#nilLiteral:
@@ -688,7 +690,7 @@ func testSameType() {
testSugarType(.#^SUGAR_TYPE^#
// Ensure results aren't duplicated.
// SUGAR_TYPE: Begin completions, 9 items
// SUGAR_TYPE: Begin completions, 10 items
// SUGAR_TYPE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: South[#SomeEnum1#];
// SUGAR_TYPE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: North[#SomeEnum1#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#];
@@ -697,6 +699,7 @@ func testSameType() {
// SUGAR_TYPE-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Convertible]: some({#SomeEnum1#})[#Optional<SomeEnum1>#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: map({#(self): Optional<SomeEnum1>#})[#((SomeEnum1) throws(Error) -> ~Copyable) -> ~Copyable?#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: flatMap({#(self): Optional<SomeEnum1>#})[#((SomeEnum1) throws(Error) -> ~Copyable?) -> ~Copyable?#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: take({#(self): &Optional<SomeEnum1>#})[#() -> Optional<SomeEnum1>#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<SomeEnum1>#})[#(into: inout Hasher) -> Void#];
}

View File

@@ -0,0 +1,293 @@
//===--- Algorithms.swift -------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 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: %target-build-swift -swift-version 6 -o %t/a.out %s
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out
// REQUIRES: executable_test
import StdlibUnittest
import Synchronization
struct Hypoarray<Element: ~Copyable>: ~Copyable {
private var _storage: UnsafeMutableBufferPointer<Element>
private var _count: Int
var capacity: Int { _storage.count }
init() {
_storage = .init(start: nil, count: 0)
_count = 0
}
init(_ element: consuming Element) {
_storage = .allocate(capacity: 1)
_storage.initializeElement(at: 0, to: element)
_count = 1
}
init(count: Int, initializedBy generator: (Int) -> Element) {
_storage = .allocate(capacity: count)
for i in 0 ..< count {
_storage.initializeElement(at: i, to: generator(i))
}
_count = count
}
deinit {
_storage.extracting(0 ..< count).deinitialize()
_storage.deallocate()
}
}
extension Hypoarray: @unchecked Sendable where Element: Sendable & ~Copyable {}
extension Hypoarray where Element: ~Copyable {
typealias Index = Int
var isEmpty: Bool { _count == 0 }
var count: Int { _count }
var startIndex: Int { 0 }
var endIndex: Int { _count }
func index(after i: Int) -> Int { i + 1 }
func index(before i: Int) -> Int { i - 1 }
func distance(from start: Int, to end: Int) -> Int { end - start }
// etc.
}
extension Hypoarray where Element: ~Copyable {
func borrowElement<E: Error, R: ~Copyable> (
at index: Int,
by body: (borrowing Element) throws(E) -> R
) throws(E) -> R {
precondition(index >= 0 && index < _count)
return try body(_storage[index])
}
mutating func updateElement<E: Error, R: ~Copyable> (
at index: Int,
by body: (inout Element) throws(E) -> R
) throws(E) -> R {
precondition(index >= 0 && index < _count)
return try body(&_storage[index])
}
}
extension Hypoarray where Element: ~Copyable {
subscript(position: Int) -> Element {
_read {
precondition(position >= 0 && position < _count)
yield _storage[position]
}
_modify {
precondition(position >= 0 && position < _count)
yield &_storage[position]
}
}
}
extension Hypoarray where Element: ~Copyable {
@discardableResult
mutating func remove(at index: Int) -> Element {
precondition(index >= 0 && index < count)
let old = _storage.moveElement(from: index)
let source = _storage.extracting(index + 1 ..< count)
let target = _storage.extracting(index ..< count - 1)
let i = target.moveInitialize(fromContentsOf: source)
assert(i == target.endIndex)
_count -= 1
return old
}
}
extension Hypoarray where Element: ~Copyable {
mutating func reserveCapacity(_ n: Int) {
guard capacity < n else { return }
let newStorage: UnsafeMutableBufferPointer<Element> = .allocate(capacity: n)
let source = _storage.extracting(0 ..< count)
let i = newStorage.moveInitialize(fromContentsOf: source)
assert(i == count)
_storage.deallocate()
_storage = newStorage
}
}
extension Hypoarray where Element: ~Copyable {
mutating func _ensureFreeCapacity(_ minimumCapacity: Int) {
guard capacity < _count + minimumCapacity else { return }
reserveCapacity(max(_count + minimumCapacity, 2 * capacity))
}
}
extension Hypoarray where Element: ~Copyable {
mutating func append(_ item: consuming Element) {
_ensureFreeCapacity(1)
_storage.initializeElement(at: _count, to: item)
_count += 1
}
}
extension Hypoarray where Element: ~Copyable {
mutating func insert(_ item: consuming Element, at index: Int) {
precondition(index >= 0 && index <= count)
_ensureFreeCapacity(1)
if index < count {
let source = _storage.extracting(index ..< count)
let target = _storage.extracting(index + 1 ..< count + 1)
let last = target.moveInitialize(fromContentsOf: source)
assert(last == target.endIndex)
}
_storage.initializeElement(at: index, to: item)
_count += 1
}
}
extension Hypoarray {
mutating func append(contentsOf items: some Sequence<Element>) {
for item in items {
append(item)
}
}
}
struct Counted: ~Copyable {
var value: Int
nonisolated(unsafe) static var instances: Int = 0
init(_ value: Int) {
self.value = value
Counted.instances += 1
}
deinit {
Counted.instances -= 1
expectGE(Counted.instances, 0)
}
}
var suite = TestSuite("Hypoarray")
defer { runAllTests() }
suite.test("basics") {
var array = Hypoarray(Counted(42))
expectFalse(array.isEmpty)
expectEqual(array.count, 1)
expectEqual(array[0].value, 42)
expectEqual(Counted.instances, 1)
array.append(Counted(23))
expectFalse(array.isEmpty)
expectEqual(array.count, 2)
expectEqual(array[0].value, 42)
expectEqual(array[1].value, 23)
expectEqual(Counted.instances, 2)
let old = array.remove(at: 0)
expectEqual(old.value, 42)
expectFalse(array.isEmpty)
expectEqual(array.count, 1)
expectEqual(array[0].value, 23)
expectEqual(Counted.instances, 2)
_ = consume old
expectEqual(Counted.instances, 1)
let old2 = array.remove(at: 0)
expectEqual(old2.value, 23)
expectEqual(array.count, 0)
expectTrue(array.isEmpty)
expectEqual(Counted.instances, 1)
_ = consume old2
expectEqual(Counted.instances, 0)
}
suite.test("read access") {
let c = 100
let array = Hypoarray<Counted>(count: c) { Counted($0) }
for i in 0 ..< c {
expectEqual(array.borrowElement(at: i) { $0.value }, i)
expectEqual(array[i].value, i)
}
}
suite.test("update access") {
let c = 100
var array = Hypoarray<Counted>(count: c) { Counted($0) }
for i in 0 ..< c {
array.updateElement(at: i) { $0.value += 100 }
array[i].value += 100
}
for i in 0 ..< c {
expectEqual(array[i].value, 200 + i)
}
expectEqual(Counted.instances, c)
_ = consume array
expectEqual(Counted.instances, 0)
}
suite.test("append") {
var array = Hypoarray<Counted>()
let c = 100
for i in 0 ..< c {
array.append(Counted(100 + i))
}
expectEqual(Counted.instances, c)
expectEqual(array.count, c)
for i in 0 ..< c {
// FIXME: unexpected exclusivity violation (rdar://128441125)
//expectEqual(array.borrowElement(at: i) { $0.value }, 100 + i)
expectEqual(array[i].value, 100 + i)
}
_ = consume array
expectEqual(Counted.instances, 0)
}
suite.test("insert") {
var array = Hypoarray<Counted>()
let c = 100
for i in 0 ..< c {
array.insert(Counted(100 + i), at: 0)
}
expectEqual(Counted.instances, c)
expectEqual(array.count, c)
for i in 0 ..< c {
// FIXME: unexpected exclusivity violation (rdar://128441125)
//expectEqual(array.borrowElement(at: i) { $0.value }, c + 99 - i)
expectEqual(array[i].value, c + 99 - i)
}
_ = consume array
expectEqual(Counted.instances, 0)
}
suite.test("remove") {
let c = 100
var array = Hypoarray<Counted>(count: c) { Counted(100 + $0) }
expectEqual(Counted.instances, c)
expectEqual(array.count, c)
for i in 0 ..< c {
array.remove(at: 0)
expectEqual(array.count, c - 1 - i)
expectEqual(Counted.instances, c - 1 - i)
}
expectTrue(array.isEmpty)
expectEqual(Counted.instances, 0)
}

78
test/stdlib/swap.swift Normal file
View File

@@ -0,0 +1,78 @@
// RUN: %target-run-simple-swift
// REQUIRES: executable_test
import StdlibUnittest
var suite = TestSuite("Unmanaged")
defer { runAllTests() }
struct Counted: ~Copyable {
let value: Int
static var instances: Int = 0
init(_ value: Int) {
self.value = value
Counted.instances += 1
}
deinit {
Counted.instances -= 1
expectGE(Counted.instances, 0)
}
}
suite.test("swap.Int") {
var a = 1
var b = 2
swap(&a, &b)
expectEqual(a, 2)
expectEqual(b, 1)
}
suite.test("exchange.Int") {
var a = 1
let old = exchange(&a, with: 2)
expectEqual(old, 1)
expectEqual(a, 2)
}
suite.test("swap.class") {
var a = LifetimeTracked(10)
var b = LifetimeTracked(20)
swap(&a, &b)
expectEqual(LifetimeTracked.instances, 2)
expectEqual(a.value, 20)
expectEqual(b.value, 10)
}
suite.test("exchange.class") {
var a = LifetimeTracked(10)
let old = exchange(&a, with: LifetimeTracked(20))
expectEqual(LifetimeTracked.instances, 2)
expectEqual(old.value, 10)
expectEqual(a.value, 20)
}
suite.test("swap.noncopyable") {
var a = Counted(10)
var b = Counted(20)
expectEqual(Counted.instances, 2)
swap(&a, &b)
expectEqual(Counted.instances, 2)
expectEqual(a.value, 20)
expectEqual(b.value, 10)
_ = consume a
_ = consume b
expectEqual(Counted.instances, 0)
}
suite.test("exchange.noncopyable") {
var a = Counted(10)
let old = exchange(&a, with: Counted(20))
expectEqual(Counted.instances, 2)
expectEqual(a.value, 20)
expectEqual(old.value, 10)
_ = consume old
_ = consume a
expectEqual(Counted.instances, 0)
}