mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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#];
|
||||
}
|
||||
|
||||
|
||||
293
test/Prototypes/Hypoarray.swift
Normal file
293
test/Prototypes/Hypoarray.swift
Normal 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
78
test/stdlib/swap.swift
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user