mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
_modify mustn’t leave Collection storage in an inconsistent state when the code to which we’re yielding happens to throw. In practice this means that any cleanup code must happen in defer blocks before the yield. Fix Dictionary to do just that and add tests to cover this functionality (as well as some other aspects of _modify).
486 lines
13 KiB
Swift
486 lines
13 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// This protocol is only used for compile-time checks that
|
|
/// every buffer type implements all required operations.
|
|
internal protocol _DictionaryBuffer {
|
|
associatedtype Key
|
|
associatedtype Value
|
|
associatedtype Index
|
|
|
|
var startIndex: Index { get }
|
|
var endIndex: Index { get }
|
|
func index(after i: Index) -> Index
|
|
func index(forKey key: Key) -> Index?
|
|
var count: Int { get }
|
|
|
|
func contains(_ key: Key) -> Bool
|
|
func lookup(_ key: Key) -> Value?
|
|
func lookup(_ index: Index) -> (key: Key, value: Value)
|
|
func key(at index: Index) -> Key
|
|
func value(at index: Index) -> Value
|
|
}
|
|
|
|
extension Dictionary {
|
|
@usableFromInline
|
|
@_fixed_layout
|
|
internal struct _Variant {
|
|
@usableFromInline
|
|
internal var object: _BridgeStorage<_RawDictionaryStorage>
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
init(native: __owned _NativeDictionary<Key, Value>) {
|
|
self.object = _BridgeStorage(native: native._storage)
|
|
}
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
init(dummy: Void) {
|
|
#if arch(i386) || arch(arm)
|
|
self.init(native: _NativeDictionary())
|
|
#else
|
|
self.object = _BridgeStorage(taggedPayload: 0)
|
|
#endif
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
@inlinable
|
|
@inline(__always)
|
|
init(cocoa: __owned _CocoaDictionary) {
|
|
self.object = _BridgeStorage(objC: cocoa.object)
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
extension Dictionary._Variant {
|
|
#if _runtime(_ObjC)
|
|
@usableFromInline @_transparent
|
|
internal var guaranteedNative: Bool {
|
|
return _canBeClass(Key.self) == 0 || _canBeClass(Value.self) == 0
|
|
}
|
|
#endif
|
|
|
|
@inlinable
|
|
internal mutating func isUniquelyReferenced() -> Bool {
|
|
return object.isUniquelyReferencedUnflaggedNative()
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
@usableFromInline @_transparent
|
|
internal var isNative: Bool {
|
|
if guaranteedNative { return true }
|
|
return object.isUnflaggedNative
|
|
}
|
|
#endif
|
|
|
|
@usableFromInline @_transparent
|
|
internal var asNative: _NativeDictionary<Key, Value> {
|
|
get {
|
|
return _NativeDictionary<Key, Value>(object.unflaggedNativeInstance)
|
|
}
|
|
set {
|
|
self = .init(native: newValue)
|
|
}
|
|
_modify {
|
|
var native = _NativeDictionary<Key, Value>(object.unflaggedNativeInstance)
|
|
self = .init(dummy: ())
|
|
defer { object = .init(native: native._storage) }
|
|
yield &native
|
|
}
|
|
}
|
|
|
|
#if _runtime(_ObjC)
|
|
@inlinable
|
|
internal var asCocoa: _CocoaDictionary {
|
|
return _CocoaDictionary(object.objCInstance)
|
|
}
|
|
#endif
|
|
|
|
/// Reserves enough space for the specified number of elements to be stored
|
|
/// without reallocating additional storage.
|
|
internal mutating func reserveCapacity(_ capacity: Int) {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoa = asCocoa
|
|
let capacity = Swift.max(cocoa.count, capacity)
|
|
self = .init(native: _NativeDictionary(cocoa, capacity: capacity))
|
|
return
|
|
}
|
|
#endif
|
|
let isUnique = isUniquelyReferenced()
|
|
asNative.reserveCapacity(capacity, isUnique: isUnique)
|
|
}
|
|
|
|
/// The number of elements that can be stored without expanding the current
|
|
/// storage.
|
|
///
|
|
/// For bridged storage, this is equal to the current count of the
|
|
/// collection, since any addition will trigger a copy of the elements into
|
|
/// newly allocated storage. For native storage, this is the element count
|
|
/// at which adding any more elements will exceed the load factor.
|
|
@inlinable
|
|
internal var capacity: Int {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
return asCocoa.count
|
|
}
|
|
#endif
|
|
return asNative.capacity
|
|
}
|
|
}
|
|
|
|
extension Dictionary._Variant: _DictionaryBuffer {
|
|
@usableFromInline
|
|
internal typealias Element = (key: Key, value: Value)
|
|
@usableFromInline
|
|
internal typealias Index = Dictionary<Key, Value>.Index
|
|
|
|
@inlinable
|
|
internal var startIndex: Index {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
return Index(_cocoa: asCocoa.startIndex)
|
|
}
|
|
#endif
|
|
return asNative.startIndex
|
|
}
|
|
|
|
@inlinable
|
|
internal var endIndex: Index {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
return Index(_cocoa: asCocoa.endIndex)
|
|
}
|
|
#endif
|
|
return asNative.endIndex
|
|
}
|
|
|
|
@inlinable
|
|
internal func index(after index: Index) -> Index {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
return Index(_cocoa: asCocoa.index(after: index._asCocoa))
|
|
}
|
|
#endif
|
|
return asNative.index(after: index)
|
|
}
|
|
|
|
@inlinable
|
|
internal func formIndex(after index: inout Index) {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let isUnique = index._isUniquelyReferenced()
|
|
asCocoa.formIndex(after: &index._asCocoa, isUnique: isUnique)
|
|
return
|
|
}
|
|
#endif
|
|
index = asNative.index(after: index)
|
|
}
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
internal func index(forKey key: Key) -> Index? {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoaKey = _bridgeAnythingToObjectiveC(key)
|
|
guard let index = asCocoa.index(forKey: cocoaKey) else { return nil }
|
|
return Index(_cocoa: index)
|
|
}
|
|
#endif
|
|
return asNative.index(forKey: key)
|
|
}
|
|
|
|
@inlinable
|
|
internal var count: Int {
|
|
@inline(__always)
|
|
get {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
return asCocoa.count
|
|
}
|
|
#endif
|
|
return asNative.count
|
|
}
|
|
}
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
func contains(_ key: Key) -> Bool {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoaKey = _bridgeAnythingToObjectiveC(key)
|
|
return asCocoa.contains(cocoaKey)
|
|
}
|
|
#endif
|
|
return asNative.contains(key)
|
|
}
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
func lookup(_ key: Key) -> Value? {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoaKey = _bridgeAnythingToObjectiveC(key)
|
|
guard let cocoaValue = asCocoa.lookup(cocoaKey) else { return nil }
|
|
return _forceBridgeFromObjectiveC(cocoaValue, Value.self)
|
|
}
|
|
#endif
|
|
return asNative.lookup(key)
|
|
}
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
func lookup(_ index: Index) -> (key: Key, value: Value) {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let (cocoaKey, cocoaValue) = asCocoa.lookup(index._asCocoa)
|
|
let nativeKey = _forceBridgeFromObjectiveC(cocoaKey, Key.self)
|
|
let nativeValue = _forceBridgeFromObjectiveC(cocoaValue, Value.self)
|
|
return (nativeKey, nativeValue)
|
|
}
|
|
#endif
|
|
return asNative.lookup(index)
|
|
}
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
func key(at index: Index) -> Key {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoaKey = asCocoa.key(at: index._asCocoa)
|
|
return _forceBridgeFromObjectiveC(cocoaKey, Key.self)
|
|
}
|
|
#endif
|
|
return asNative.key(at: index)
|
|
}
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
func value(at index: Index) -> Value {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoaValue = asCocoa.value(at: index._asCocoa)
|
|
return _forceBridgeFromObjectiveC(cocoaValue, Value.self)
|
|
}
|
|
#endif
|
|
return asNative.value(at: index)
|
|
}
|
|
}
|
|
|
|
extension Dictionary._Variant {
|
|
@inlinable
|
|
internal subscript(key: Key) -> Value? {
|
|
@inline(__always)
|
|
get {
|
|
return lookup(key)
|
|
}
|
|
@inline(__always)
|
|
_modify {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoa = asCocoa
|
|
var native = _NativeDictionary<Key, Value>(
|
|
cocoa, capacity: cocoa.count + 1)
|
|
self = .init(native: native)
|
|
yield &native[key, isUnique: true]
|
|
return
|
|
}
|
|
#endif
|
|
let isUnique = isUniquelyReferenced()
|
|
yield &asNative[key, isUnique: isUnique]
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Dictionary._Variant {
|
|
/// Same as find(_:), except assume a corresponding key/value pair will be
|
|
/// inserted if it doesn't already exist, and mutated if it does exist. When
|
|
/// this function returns, the storage is guaranteed to be native, uniquely
|
|
/// held, and with enough capacity for a single insertion (if the key isn't
|
|
/// already in the dictionary.)
|
|
@inlinable
|
|
@inline(__always)
|
|
internal mutating func mutatingFind(
|
|
_ key: Key
|
|
) -> (bucket: _NativeDictionary<Key, Value>.Bucket, found: Bool) {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoa = asCocoa
|
|
var native = _NativeDictionary<Key, Value>(
|
|
cocoa, capacity: cocoa.count + 1)
|
|
let result = native.mutatingFind(key, isUnique: true)
|
|
self = .init(native: native)
|
|
return result
|
|
}
|
|
#endif
|
|
let isUnique = isUniquelyReferenced()
|
|
return asNative.mutatingFind(key, isUnique: isUnique)
|
|
}
|
|
|
|
@inlinable
|
|
@inline(__always)
|
|
internal mutating func ensureUniqueNative() -> _NativeDictionary<Key, Value> {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let native = _NativeDictionary<Key, Value>(asCocoa)
|
|
self = .init(native: native)
|
|
return native
|
|
}
|
|
#endif
|
|
let isUnique = isUniquelyReferenced()
|
|
if !isUnique {
|
|
asNative.copy()
|
|
}
|
|
return asNative
|
|
}
|
|
|
|
@inlinable
|
|
internal mutating func updateValue(
|
|
_ value: __owned Value,
|
|
forKey key: Key
|
|
) -> Value? {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
// Make sure we have space for an extra element.
|
|
let cocoa = asCocoa
|
|
var native = _NativeDictionary<Key, Value>(
|
|
cocoa,
|
|
capacity: cocoa.count + 1)
|
|
let result = native.updateValue(value, forKey: key, isUnique: true)
|
|
self = .init(native: native)
|
|
return result
|
|
}
|
|
#endif
|
|
let isUnique = self.isUniquelyReferenced()
|
|
return asNative.updateValue(value, forKey: key, isUnique: isUnique)
|
|
}
|
|
|
|
@inlinable
|
|
internal mutating func setValue(_ value: __owned Value, forKey key: Key) {
|
|
#if _runtime(_ObjC)
|
|
if !isNative {
|
|
// Make sure we have space for an extra element.
|
|
let cocoa = asCocoa
|
|
self = .init(native: _NativeDictionary<Key, Value>(
|
|
cocoa,
|
|
capacity: cocoa.count + 1))
|
|
}
|
|
#endif
|
|
let isUnique = self.isUniquelyReferenced()
|
|
asNative.setValue(value, forKey: key, isUnique: isUnique)
|
|
}
|
|
|
|
@inlinable
|
|
internal mutating func remove(at index: Index) -> Element {
|
|
// FIXME(performance): fuse data migration and element deletion into one
|
|
// operation.
|
|
let native = ensureUniqueNative()
|
|
let bucket = native.validatedBucket(for: index)
|
|
return asNative.uncheckedRemove(at: bucket, isUnique: true)
|
|
}
|
|
|
|
@inlinable
|
|
internal mutating func removeValue(forKey key: Key) -> Value? {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
let cocoaKey = _bridgeAnythingToObjectiveC(key)
|
|
let cocoa = asCocoa
|
|
guard cocoa.lookup(cocoaKey) != nil else { return nil }
|
|
var native = _NativeDictionary<Key, Value>(cocoa)
|
|
let (bucket, found) = native.find(key)
|
|
_precondition(found, "Bridging did not preserve equality")
|
|
let old = native.uncheckedRemove(at: bucket, isUnique: true).value
|
|
self = .init(native: native)
|
|
return old
|
|
}
|
|
#endif
|
|
let (bucket, found) = asNative.find(key)
|
|
guard found else { return nil }
|
|
let isUnique = isUniquelyReferenced()
|
|
return asNative.uncheckedRemove(at: bucket, isUnique: isUnique).value
|
|
}
|
|
|
|
@inlinable
|
|
internal mutating func removeAll(keepingCapacity keepCapacity: Bool) {
|
|
if !keepCapacity {
|
|
self = .init(native: _NativeDictionary())
|
|
return
|
|
}
|
|
guard count > 0 else { return }
|
|
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
self = .init(native: _NativeDictionary(capacity: asCocoa.count))
|
|
return
|
|
}
|
|
#endif
|
|
let isUnique = isUniquelyReferenced()
|
|
asNative.removeAll(isUnique: isUnique)
|
|
}
|
|
}
|
|
|
|
extension Dictionary._Variant {
|
|
/// Returns an iterator over the `(Key, Value)` pairs.
|
|
///
|
|
/// - Complexity: O(1).
|
|
@inlinable
|
|
@inline(__always)
|
|
__consuming internal func makeIterator() -> Dictionary<Key, Value>.Iterator {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
return Dictionary.Iterator(_cocoa: asCocoa.makeIterator())
|
|
}
|
|
#endif
|
|
return Dictionary.Iterator(_native: asNative.makeIterator())
|
|
}
|
|
}
|
|
|
|
extension Dictionary._Variant {
|
|
@inlinable
|
|
internal func mapValues<T>(
|
|
_ transform: (Value) throws -> T
|
|
) rethrows -> _NativeDictionary<Key, T> {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
return try asCocoa.mapValues(transform)
|
|
}
|
|
#endif
|
|
return try asNative.mapValues(transform)
|
|
}
|
|
|
|
@inlinable
|
|
internal mutating func merge<S: Sequence>(
|
|
_ keysAndValues: __owned S,
|
|
uniquingKeysWith combine: (Value, Value) throws -> Value
|
|
) rethrows where S.Element == (Key, Value) {
|
|
#if _runtime(_ObjC)
|
|
guard isNative else {
|
|
var native = _NativeDictionary<Key, Value>(asCocoa)
|
|
try native.merge(
|
|
keysAndValues,
|
|
isUnique: true,
|
|
uniquingKeysWith: combine)
|
|
self = .init(native: native)
|
|
return
|
|
}
|
|
#endif
|
|
let isUnique = isUniquelyReferenced()
|
|
try asNative.merge(
|
|
keysAndValues,
|
|
isUnique: isUnique,
|
|
uniquingKeysWith: combine)
|
|
}
|
|
}
|
|
|