From 379e61867a172e23135a076750391c14f0753205 Mon Sep 17 00:00:00 2001 From: elppaaa Date: Sun, 6 Jul 2025 00:58:31 +0900 Subject: [PATCH 1/2] =?UTF-8?q?OrderedCollections=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MetaCodableMacroPluginCore.podspec | 12 +- Package.swift | 2 - Package@swift-5.swift | 2 - Sources/PluginCore/Attributes/CodedAs.swift | 1 - .../HashTable/_HashTable+Bucket.swift | 37 + .../HashTable/_HashTable+BucketIterator.swift | 265 +++++ .../HashTable/_HashTable+Constants.swift | 97 ++ .../_HashTable+CustomStringConvertible.swift | 61 + .../HashTable/_HashTable+Testing.swift | 63 + .../HashTable/_HashTable+UnsafeHandle.swift | 542 +++++++++ .../HashTable/_HashTable.swift | 202 ++++ .../HashTable/_Hashtable+Header.swift | 99 ++ .../Extensions/OrderedDictionary.Elements.md | 37 + .../Extensions/OrderedDictionary.Values.md | 24 + .../Extensions/OrderedDictionary.md | 94 ++ .../Extensions/OrderedSet.UnorderedView.md | 53 + .../Extensions/OrderedSet.md | 133 +++ .../OrderedCollections.md | 10 + .../OrderedDictionary+Codable.swift | 87 ++ ...tionary+CustomDebugStringConvertible.swift | 44 + .../OrderedDictionary+CustomReflectable.swift | 17 + ...edDictionary+CustomStringConvertible.swift | 29 + .../OrderedDictionary+Deprecations.swift | 52 + ...deredDictionary+Elements+SubSequence.swift | 330 ++++++ .../OrderedDictionary+Elements.swift | 653 +++++++++++ .../OrderedDictionary+Equatable.swift | 23 + ...onary+ExpressibleByDictionaryLiteral.swift | 31 + .../OrderedDictionary+Hashable.swift | 25 + .../OrderedDictionary+Initializers.swift | 455 +++++++ .../OrderedDictionary+Invariants.swift | 23 + ...Dictionary+Partial MutableCollection.swift | 193 +++ ...y+Partial RangeReplaceableCollection.swift | 184 +++ .../OrderedDictionary+Sequence.swift | 63 + .../OrderedDictionary+Values.swift | 381 ++++++ .../OrderedDictionary/OrderedDictionary.swift | 1044 +++++++++++++++++ .../OrderedSet/OrderedSet+Codable.swift | 46 + ...eredSet+CustomDebugStringConvertible.swift | 36 + .../OrderedSet+CustomReflectable.swift | 17 + .../OrderedSet+CustomStringConvertible.swift | 28 + .../OrderedSet/OrderedSet+Diffing.swift | 100 ++ .../OrderedSet/OrderedSet+Equatable.swift | 23 + ...OrderedSet+ExpressibleByArrayLiteral.swift | 32 + .../OrderedSet/OrderedSet+Hashable.swift | 24 + .../OrderedSet/OrderedSet+Initializers.swift | 152 +++ .../OrderedSet/OrderedSet+Insertions.swift | 255 ++++ .../OrderedSet/OrderedSet+Invariants.swift | 54 + ...OrderedSet+Partial MutableCollection.swift | 442 +++++++ ...t+Partial RangeReplaceableCollection.swift | 224 ++++ ...OrderedSet+Partial SetAlgebra+Basics.swift | 76 ++ ...redSet+Partial SetAlgebra+Operations.swift | 589 ++++++++++ ...redSet+Partial SetAlgebra+Predicates.swift | 551 +++++++++ .../OrderedSet+RandomAccessCollection.swift | 300 +++++ .../OrderedSet+ReserveCapacity.swift | 129 ++ .../OrderedSet/OrderedSet+SubSequence.swift | 355 ++++++ .../OrderedSet/OrderedSet+UnorderedView.swift | 987 ++++++++++++++++ .../OrderedSet+UnstableInternals.swift | 48 + .../OrderedSet/OrderedSet.swift | 482 ++++++++ .../RandomAccessCollection+Offsets.swift | 30 + .../Utilities/_UnsafeBitset.swift | 395 +++++++ .../Property/AliasedPropertyVariable.swift | 1 - .../PropertyVariableTreeNode+CodingData.swift | 1 - .../Tree/PropertyVariableTreeNode.swift | 1 - .../Data/CodingKeysMap/CodingKeysMap.swift | 1 - Utils/spec.rb | 1 + Vendor/swift-collections | 1 + 65 files changed, 10738 insertions(+), 11 deletions(-) create mode 100644 Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Bucket.swift create mode 100644 Sources/PluginCore/OrderedCollections/HashTable/_HashTable+BucketIterator.swift create mode 100644 Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Constants.swift create mode 100644 Sources/PluginCore/OrderedCollections/HashTable/_HashTable+CustomStringConvertible.swift create mode 100644 Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Testing.swift create mode 100644 Sources/PluginCore/OrderedCollections/HashTable/_HashTable+UnsafeHandle.swift create mode 100644 Sources/PluginCore/OrderedCollections/HashTable/_HashTable.swift create mode 100644 Sources/PluginCore/OrderedCollections/HashTable/_Hashtable+Header.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.Elements.md create mode 100644 Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.Values.md create mode 100644 Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.md create mode 100644 Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedSet.UnorderedView.md create mode 100644 Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedSet.md create mode 100644 Sources/PluginCore/OrderedCollections/OrderedCollections.docc/OrderedCollections.md create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Codable.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomDebugStringConvertible.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomReflectable.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomStringConvertible.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Deprecations.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements+SubSequence.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Equatable.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+ExpressibleByDictionaryLiteral.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Hashable.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Initializers.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Invariants.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Partial MutableCollection.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Partial RangeReplaceableCollection.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Sequence.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Values.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Codable.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomDebugStringConvertible.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomReflectable.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomStringConvertible.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Diffing.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Equatable.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+ExpressibleByArrayLiteral.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Hashable.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Initializers.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Insertions.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Invariants.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial MutableCollection.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial RangeReplaceableCollection.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Basics.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Operations.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Predicates.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+RandomAccessCollection.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+ReserveCapacity.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+SubSequence.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+UnorderedView.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+UnstableInternals.swift create mode 100644 Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet.swift create mode 100644 Sources/PluginCore/OrderedCollections/Utilities/RandomAccessCollection+Offsets.swift create mode 100644 Sources/PluginCore/OrderedCollections/Utilities/_UnsafeBitset.swift create mode 160000 Vendor/swift-collections diff --git a/MetaCodableMacroPluginCore.podspec b/MetaCodableMacroPluginCore.podspec index e7a20232ed..e1a888e528 100644 --- a/MetaCodableMacroPluginCore.podspec +++ b/MetaCodableMacroPluginCore.podspec @@ -2,12 +2,20 @@ Pod::Spec.new do |s| require_relative 'Utils/spec' s.extend MetaCodable::Spec s.module_name = "PluginCore" - s.define + s.define(false) # Don't auto-set source_files s.dependency 'SwiftSyntax/Lib', *s.swift_syntax_constraint s.dependency 'SwiftSyntax/Diagnostics', *s.swift_syntax_constraint s.dependency 'SwiftSyntax/Builder', *s.swift_syntax_constraint s.dependency 'SwiftSyntax/Macros', *s.swift_syntax_constraint - s.dependency 'SwiftyCollections/OrderedCollections', '~> 1.0.4' + + # Use vendored swift-collections instead of external dependency + # Previously: s.dependency 'SwiftyCollections/OrderedCollections', '~> 1.0.4' + + # Include PluginCore sources (now includes OrderedCollections internally) + s.source_files = "Sources/#{s.module_name}/**/*.swift" + + # Exclude test files from OrderedCollections + s.exclude_files = "Sources/#{s.module_name}/OrderedCollections/**/*+Testing.swift" s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => "-Xfrontend -package-name -Xfrontend MetaCodable" diff --git a/Package.swift b/Package.swift index b7352bd3c5..02cc7fd4c4 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,6 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/swiftlang/swift-syntax.git", "509.1.0"..<"602.0.0"), - .package(url: "https://github.com/apple/swift-collections.git", from: "1.0.4"), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"), .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0"), ], @@ -35,7 +34,6 @@ let package = Package( .product(name: "SwiftDiagnostics", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - .product(name: "OrderedCollections", package: "swift-collections"), ] ), diff --git a/Package@swift-5.swift b/Package@swift-5.swift index b0a38bf069..00e31353f3 100644 --- a/Package@swift-5.swift +++ b/Package@swift-5.swift @@ -21,7 +21,6 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/swiftlang/swift-syntax.git", "509.1.0"..<"602.0.0"), - .package(url: "https://github.com/apple/swift-collections.git", from: "1.0.4"), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"), ], targets: [ @@ -34,7 +33,6 @@ let package = Package( .product(name: "SwiftDiagnostics", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - .product(name: "OrderedCollections", package: "swift-collections"), ] ), diff --git a/Sources/PluginCore/Attributes/CodedAs.swift b/Sources/PluginCore/Attributes/CodedAs.swift index 4e2021ea03..7eddcf137a 100644 --- a/Sources/PluginCore/Attributes/CodedAs.swift +++ b/Sources/PluginCore/Attributes/CodedAs.swift @@ -1,4 +1,3 @@ -import OrderedCollections import SwiftSyntax import SwiftSyntaxMacros diff --git a/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Bucket.swift b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Bucket.swift new file mode 100644 index 0000000000..b912fe831a --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Bucket.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension _HashTable { + /// Identifies a particular bucket within a hash table by its offset. + /// Having a dedicated wrapper type for this prevents passing a bucket number + /// to a function that expects a word index, or vice versa. + @usableFromInline + @frozen + internal struct Bucket { + /// The distance of this bucket from the first bucket in the hash table. + @usableFromInline + internal var offset: Int + + @inlinable + @inline(__always) + internal init(offset: Int) { + assert(offset >= 0) + self.offset = offset + } + } +} + +extension _HashTable.Bucket: Equatable { + @_transparent + public static func == (left: Self, right: Self) -> Bool { + left.offset == right.offset + } +} diff --git a/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+BucketIterator.swift b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+BucketIterator.swift new file mode 100644 index 0000000000..c4a62485eb --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+BucketIterator.swift @@ -0,0 +1,265 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension _HashTable { + /// An iterator construct for visiting a chain of buckets within the hash + /// table. This is a convenient tool for implementing linear probing. + /// + /// Beyond merely providing bucket values, bucket iterators can also tell + /// you their current oposition within the hash table, and (for mutable hash + /// tables) they allow you update the value of the currently visited bucket. + /// (This is useful when implementing simple insertions, for example.) + /// + /// The bucket iterator caches some bucket contents, so if you are looping + /// over an iterator you must be careful to only modify hash table contents + /// through the iterator itself. + /// + /// - Warning: Like `UnsafeHandle`, `BucketIterator` does not have + /// ownership of its underlying storage buffer. You must not escape + /// iterator values outside the closure call that produced the original + /// hash table. + @usableFromInline + internal struct BucketIterator { + @usableFromInline + internal typealias Bucket = _HashTable.Bucket + + /// The hash table we are iterating over. + internal let _hashTable: _UnsafeHashTable + + /// The current position within the hash table. + @usableFromInline + internal var _currentBucket: Bucket + + /// The raw bucket value corresponding to `_currentBucket`. + internal var _currentRawValue: UInt64 + + /// Remaining bits not yet processed from the last word read. + internal var _nextBits: UInt64 + + /// Count of usable bits in `_nextBits`. (They start at bit 0.) + internal var _remainingBitCount: Int + + internal var _wrappedAround = false + + /// Create a new iterator starting at the specified bucket. + @_effects(releasenone) + @usableFromInline + internal init(hashTable: _UnsafeHashTable, startingAt bucket: Bucket) { + assert(hashTable.scale >= _HashTable.minimumScale) + assert(bucket.offset >= 0 && bucket.offset < hashTable.bucketCount) + self._hashTable = hashTable + self._currentBucket = bucket + (self._currentRawValue, self._nextBits, self._remainingBitCount) + = hashTable._startIterator(bucket: bucket) + } + } +} + +extension _HashTable.UnsafeHandle { + @usableFromInline + internal typealias BucketIterator = _HashTable.BucketIterator + + @_effects(releasenone) + @inlinable + @inline(__always) + internal func idealBucket(forHashValue hashValue: Int) -> Bucket { + return Bucket(offset: hashValue & (bucketCount - 1)) + } + + @inlinable + @inline(__always) + internal func idealBucket(for element: Element) -> Bucket { + let hashValue = element._rawHashValue(seed: seed) + return idealBucket(forHashValue: hashValue) + } + + /// Return a bucket iterator for the chain starting at the bucket corresponding + /// to the specified value. + @inlinable + @inline(__always) + internal func bucketIterator(for element: Element) -> BucketIterator { + let bucket = idealBucket(for: element) + return bucketIterator(startingAt: bucket) + } + + /// Return a bucket iterator for the chain starting at the specified bucket. + @inlinable + @inline(__always) + internal func bucketIterator(startingAt bucket: Bucket) -> BucketIterator { + BucketIterator(hashTable: self, startingAt: bucket) + } + + @usableFromInline + @_effects(releasenone) + internal func startFind( + _ startBucket: Bucket + ) -> (iterator: BucketIterator, currentValue: Int?) { + let iterator = bucketIterator(startingAt: startBucket) + return (iterator, iterator.currentValue) + } + + @_effects(readonly) + @usableFromInline + internal func _startIterator( + bucket: Bucket + ) -> (currentBits: UInt64, nextBits: UInt64, remainingBitCount: Int) { + // The `scale == 5` case is special because the last word is only half filled there, + // which is why the code below needs to special case it. + // (For all scales > 5, the last bucket ends exactly on a word boundary.) + + var (word, bit) = self.position(of: bucket) + if bit + scale <= 64 { + // We're in luck, the current bucket is stored entirely within one word. + let w = self[word: word] + let currentRawValue = (w &>> bit) & bucketMask + let c = (scale == 5 && word == wordCount - 1 ? 32 : 64) + let remainingBitCount = c - (bit + scale) + let nextBits = (remainingBitCount == 0 ? 0 : w &>> (bit + scale)) + assert(remainingBitCount >= 0) + assert(bit < c) + return (currentRawValue, nextBits, remainingBitCount) + } else { + // We need to read two words. + assert(scale != 5 || word < wordCount - 1) + assert(bit > 0) + let w1 = self[word: word] + word = self.word(after: word) + let w2 = self[word: word] + let currentRawValue = ((w1 &>> bit) | (w2 &<< (64 - bit))) & bucketMask + let overhang = scale - (64 - bit) + let nextBits = w2 &>> overhang + let c = (scale == 5 && word == wordCount - 1 ? 32 : 64) + let remainingBitCount = c - overhang + return (currentRawValue, nextBits, remainingBitCount) + } + } +} + +extension _HashTable.BucketIterator { + /// The scale of the hash table. A table of scale *n* holds 2^*n* buckets, + /// each of which contain an *n*-bit value. + @inline(__always) + internal var _scale: Int { _hashTable.scale } + + /// The current position within the hash table. + @inlinable + @inline(__always) + internal var currentBucket: Bucket { _currentBucket } + + @usableFromInline + internal var isOccupied: Bool { + @_effects(readonly) + @inline(__always) + get { + _currentRawValue != 0 + } + } + + /// The value of the bucket at the current position in the hash table. + /// Setting this property overwrites the bucket value. + /// + /// A nil value indicates an empty bucket. + @usableFromInline + internal var currentValue: Int? { + @inline(__always) + @_effects(readonly) + get { _hashTable._value(forBucketContents: _currentRawValue) } + @_effects(releasenone) + set { + _hashTable.assertMutable() + let v = _hashTable._bucketContents(for: newValue) + let pattern = v ^ _currentRawValue + + assert(_currentBucket.offset < _hashTable.bucketCount) + let (word, bit) = _hashTable.position(of: _currentBucket) + _hashTable[word: word] ^= pattern &<< bit + let extractedBits = 64 - bit + if extractedBits < _scale { + let word2 = _hashTable.word(after: word) + _hashTable[word: word2] ^= pattern &>> extractedBits + } + _currentRawValue = v + } + } + + /// Advance this iterator to the next bucket within the hash table. + /// The buckets form a cycle, so the last bucket is logically followed + /// by the first. Therefore, the iterator never runs out of buckets -- + /// you must devise some way to guarantee to stop iterating. + /// + /// In the typical case, you stop iterating buckets when you find the + /// element you're looking for, or when you run across an empty bucket + /// (terminating the chain with a negative lookup result). + /// + /// To catch mistakes (and corrupt tables), `advance` traps the second + /// time it needs to wrap around to the beginning of the table. + @usableFromInline + @_effects(releasenone) + internal mutating func advance() { + // Advance to next bucket, checking for wraparound condition. + _currentBucket.offset &+= 1 + if _currentBucket.offset == _hashTable.bucketCount { + guard !_wrappedAround else { + // Prevent wasting battery in an infinite loop if a hash table + // somehow becomes corrupt. + fatalError("Hash table has no unoccupied buckets") + } + _wrappedAround = true + _currentBucket.offset = 0 + } + + // If we have loaded enough bits, eat them and return. + if _remainingBitCount >= _scale { + _currentRawValue = _nextBits & _hashTable.bucketMask + _nextBits &>>= _scale + _remainingBitCount -= _scale + return + } + + // Load the next batch of bits. + var word = _hashTable.position(of: _currentBucket).word + if _remainingBitCount != 0 { + word = _hashTable.word(after: word) + } + let c = (_hashTable.scale == 5 && word == _hashTable.wordCount - 1 ? 32 : 64) + let w = _hashTable[word: word] + _currentRawValue = (_nextBits | (w &<< _remainingBitCount)) & _hashTable.bucketMask + _nextBits = w &>> (_scale - _remainingBitCount) + _remainingBitCount = c - (_scale - _remainingBitCount) + } + + @usableFromInline + @_effects(releasenone) + internal mutating func findNext() -> Int? { + advance() + return currentValue + } + + /// Advance this iterator until it points to an occupied bucket with the + /// specified value, or an unoccupied bucket -- whichever comes first. + @inlinable + @_effects(releasenone) + internal mutating func advance(until expected: Int) { + while isOccupied && currentValue != expected { + advance() + } + } + + /// Advance this iterator until it points to an unoccupied bucket. + /// Useful when inserting an element that we know isn't already in the table. + @inlinable + @_effects(releasenone) + internal mutating func advanceToNextUnoccupiedBucket() { + while isOccupied { + advance() + } + } +} diff --git a/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Constants.swift b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Constants.swift new file mode 100644 index 0000000000..84ef2dd580 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Constants.swift @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension _HashTable { + /// The minimum hash table scale. + @usableFromInline + @inline(__always) + internal static var minimumScale: Int { + @_effects(readnone) + get { + 5 + } + } + + /// The maximum hash table scale. + @usableFromInline + @inline(__always) + internal static var maximumScale: Int { + @_effects(readnone) + get { + Swift.min(Int.bitWidth, 56) + } + } + + /// The maximum number of items for which we do not create a hash table. + @usableFromInline + @inline(__always) + internal static var maximumUnhashedCount: Int { + @_effects(readnone) + get { + (1 &<< (minimumScale - 1)) - 1 + } + } + + /// The maximum hash table load factor. + @inline(__always) + internal static var maximumLoadFactor: Double { 3 / 4 } + + /// The minimum hash table load factor. + @inline(__always) + internal static var minimumLoadFactor: Double { 1 / 4 } + + /// The maximum number of items that can be held in a hash table of the given scale. + @usableFromInline + @_effects(readnone) + internal static func minimumCapacity(forScale scale: Int) -> Int { + guard scale >= minimumScale else { return 0 } + let bucketCount = 1 &<< scale + return Int(Double(bucketCount) * minimumLoadFactor) + } + + /// The maximum number of items that can be held in a hash table of the given scale. + @usableFromInline + @_effects(readnone) + internal static func maximumCapacity(forScale scale: Int) -> Int { + guard scale >= minimumScale else { return maximumUnhashedCount } + let bucketCount = 1 &<< scale + return Int(Double(bucketCount) * maximumLoadFactor) + } + + /// The minimum hash table scale that can hold the specified number of elements. + @usableFromInline + @_effects(readnone) + internal static func scale(forCapacity capacity: Int) -> Int { + guard capacity > maximumUnhashedCount else { return 0 } + let capacity = Swift.max(capacity, 1) + // Calculate the minimum number of entries we need to allocate to satisfy + // the maximum load factor. `capacity + 1` below ensures that we always + // leave at least one hole. + let minimumEntries = Swift.max( + Int((Double(capacity) / maximumLoadFactor).rounded(.up)), + capacity + 1) + // The actual number of entries we need to allocate is the lowest power of + // two greater than or equal to the minimum entry count. Calculate its + // exponent. + let scale = (Swift.max(minimumEntries, 2) - 1)._binaryLogarithm() + 1 + assert(scale >= minimumScale && scale < Int.bitWidth) + // The scale is the exponent corresponding to the bucket count. + assert(self.maximumCapacity(forScale: scale) >= capacity) + return scale + } + + /// The count of 64-bit words that a hash table of the specified scale + /// will need to have in its storage. + internal static func wordCount(forScale scale: Int) -> Int { + ((scale &<< scale) + 63) / 64 + } +} + diff --git a/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+CustomStringConvertible.swift b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+CustomStringConvertible.swift new file mode 100644 index 0000000000..2c46631244 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+CustomStringConvertible.swift @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +// These are primarily for debugging. + +extension _HashTable.Header: CustomStringConvertible { + @usableFromInline + internal var _description: String { + "(scale: \(scale), reservedScale: \(reservedScale), bias: \(bias), seed: \(String(seed, radix: 16)))" + } + + @usableFromInline + internal var description: String { + "_HashTable.Header\(_description)" + } +} + +extension _HashTable.UnsafeHandle: CustomStringConvertible { + internal func _description(type: String) -> String { + var d = """ + \(type)\(_header.pointee._description) + load factor: \(debugLoadFactor()) + """ + if bucketCount < 128 { + d += "\n " + d += debugContents() + .lazy + .map { $0 == nil ? "_" : "\($0!)" } + .joined(separator: " ") + } + return d + } + + @usableFromInline + internal var description: String { + _description(type: "_HashTable.UnsafeHandle") + } +} + +extension _HashTable: CustomStringConvertible { + @usableFromInline + internal var description: String { + read { $0._description(type: "_HashTable") } + } +} + +extension _HashTable.Storage: CustomStringConvertible { + @usableFromInline + internal var description: String { + _HashTable(self).read { $0._description(type: "_HashTable.Storage") } + } +} + diff --git a/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Testing.swift b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Testing.swift new file mode 100644 index 0000000000..f27006fa16 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+Testing.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension _HashTable.Bucket: CustomStringConvertible { + public var description: String { "Bucket(@\(offset))"} +} + +extension _UnsafeHashTable { + internal func debugOccupiedCount() -> Int { + var count = 0 + var it = bucketIterator(startingAt: Bucket(offset: 0)) + repeat { + if it.isOccupied { + count += 1 + } + it.advance() + } while it.currentBucket.offset != 0 + return count + } + + internal func debugLoadFactor() -> Double { + return Double(debugOccupiedCount()) / Double(bucketCount) + } + + internal func debugContents() -> [Int?] { + var result: [Int?] = [] + result.reserveCapacity(bucketCount) + var it = bucketIterator(startingAt: Bucket(offset: 0)) + repeat { + result.append(it.currentValue) + it.advance() + } while it.currentBucket.offset != 0 + return result + } +} + +extension _UnsafeHashTable.BucketIterator: CustomStringConvertible { + @usableFromInline + var description: String { + func pad(_ s: String, to length: Int, by padding: Character = " ") -> String { + let c = s.count + guard c < length else { return s } + return String(repeating: padding, count: length - c) + s + } + let offset = pad(String(_currentBucket.offset), to: 4) + let value: String + if let v = currentValue { + value = pad(String(v), to: 4) + } else { + value = " nil" + } + let remainingBits = pad(String(_nextBits, radix: 2), to: _remainingBitCount, by: "0") + return "BucketIterator(scale: \(_scale), bucket: \(offset), value: \(value), bits: \(remainingBits) (\(_remainingBitCount) bits))" + } +} diff --git a/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+UnsafeHandle.swift b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+UnsafeHandle.swift new file mode 100644 index 0000000000..ba8efcb453 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable+UnsafeHandle.swift @@ -0,0 +1,542 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +@usableFromInline +internal typealias _UnsafeHashTable = _HashTable.UnsafeHandle + +extension _HashTable { + /// A non-owning handle to hash table storage, implementing higher-level + /// table operations. + /// + /// - Warning: `_UnsafeHashTable` values do not have ownership of their + /// underlying storage buffer. You must not escape these handles outside + /// the closure call that produced them. + @usableFromInline + @frozen + internal struct UnsafeHandle { + @usableFromInline + internal typealias Bucket = _HashTable.Bucket + + /// A pointer to the table header. + @usableFromInline + internal var _header: UnsafeMutablePointer
+ + /// A pointer to bucket storage. + @usableFromInline + internal var _buckets: UnsafeMutablePointer + + #if DEBUG + /// True when this handle does not support table mutations. + /// (This is only checked in debug builds.) + @usableFromInline + internal let _readonly: Bool + #endif + + /// Initialize a new hash table handle for storage at the supplied locations. + @inlinable + @inline(__always) + internal init( + header: UnsafeMutablePointer
, + buckets: UnsafeMutablePointer, + readonly: Bool + ) { + self._header = header + self._buckets = buckets + #if DEBUG + self._readonly = readonly + #endif + } + + /// Check that this handle supports mutating operations. + /// Every member that mutates table data must start by calling this function. + /// This helps preventing COW violations. + /// + /// Note that this is a noop in release builds. + @inlinable + @inline(__always) + func assertMutable() { + #if DEBUG + assert(!_readonly, "Attempt to mutate a hash table through a read-only handle") + #endif + } + } +} + +extension _HashTable.UnsafeHandle { + /// The scale of the hash table. A table of scale *n* holds 2^*n* buckets, + /// each of which contain an *n*-bit value. + @inlinable + @inline(__always) + internal var scale: Int { _header.pointee.scale } + + /// The scale corresponding to the last call to `reserveCapacity`. + /// We store this to make sure we don't shrink the table below its reserved size. + @inlinable + @inline(__always) + internal var reservedScale: Int { _header.pointee.reservedScale } + + /// The hasher seed to use within this hash table. + @inlinable + @inline(__always) + internal var seed: Int { _header.pointee.seed } + + /// A bias value that needs to be added to buckets to convert them into offsets + /// into element storage. (This allows O(1) insertions at the front when the + /// underlying storage supports it.) + @inlinable + @inline(__always) + internal var bias: Int { + get { _header.pointee.bias } + nonmutating set { _header.pointee.bias = newValue } + } + + /// The number of buckets within this hash table. This is always a power of two. + @inlinable + @inline(__always) + internal var bucketCount: Int { 1 &<< scale } + + @inlinable + @inline(__always) + internal var bucketMask: UInt64 { UInt64(truncatingIfNeeded: bucketCount) - 1 } + + /// The number of bits used to store all the buckets in this hash table. + /// Each bucket holds a value that is `scale` bits wide. + @inlinable + @inline(__always) + internal var bitCount: Int { scale &<< scale } + + /// The number of 64-bit words that are available in the storage buffer, + /// rounded up to the nearest whole number if necessary. + @inlinable + @inline(__always) + internal var wordCount: Int { (bitCount + UInt64.bitWidth - 1) / UInt64.bitWidth } + + /// The maximum number of items that can fit into this table. + @inlinable + @inline(__always) + internal var capacity: Int { _HashTable.maximumCapacity(forScale: scale) } + + /// Return the bucket logically following `bucket` in this hash table. + /// The buckets form a cycle, so the last bucket is logically followed by the first. + @inlinable + @inline(__always) + func bucket(after bucket: Bucket) -> Bucket { + var offset = bucket.offset + 1 + if offset == bucketCount { + offset = 0 + } + return Bucket(offset: offset) + } + + /// Return the bucket logically preceding `bucket` in this hash table. + /// The buckets form a cycle, so the first bucket is logically preceded by the last. + @inlinable + @inline(__always) + func bucket(before bucket: Bucket) -> Bucket { + let offset = (bucket.offset == 0 ? bucketCount : bucket.offset) - 1 + return Bucket(offset: offset) + } + + /// Return the index of the word logically following `word` in this hash table. + /// The buckets form a cycle, so the last word is logically followed by the first. + /// + /// Note that the last word may be only partially filled if `scale` is less than 6. + @inlinable + @inline(__always) + func word(after word: Int) -> Int { + var result = word + 1 + if result == wordCount { + result = 0 + } + return result + } + + /// Return the index of the word logically preceding `word` in this hash table. + /// The buckets form a cycle, so the first word is logically preceded by the first. + /// + /// Note that the last word may be only partially filled if `scale` is less than 6. + @inlinable + @inline(__always) + func word(before word: Int) -> Int { + if word == 0 { + return wordCount - 1 + } + return word - 1 + } + + /// Return the index of the 64-bit storage word that holds the first bit + /// corresponding to `bucket`, along with its bit position within the word. + @inlinable + internal func position(of bucket: Bucket) -> (word: Int, bit: Int) { + let start = bucket.offset &* scale + return (start &>> 6, start & 0x3F) + } +} + +extension _HashTable.UnsafeHandle { + /// Decode and return the logical value corresponding to the specified bucket value. + /// + /// The nil value is represented by an all-zero bit pattern. + /// Other values are stored as the complement of the lowest `scale` bits + /// after taking `bias` into account. + /// The range of representable values is `0 ..< bucketCount - 1`. + /// (Note that the value `bucketCount - 1` is missing from this range, as its + /// encoding is used for `nil`. This isn't an issue, because the maximum load + /// factor guarantees that the hash table will never be completely full.) + @inlinable + func _value(forBucketContents bucketContents: UInt64) -> Int? { + let mask = bucketMask + assert(bucketContents <= mask) + guard bucketContents != 0 else { return nil } + let v = (bucketContents ^ mask) &+ UInt64(truncatingIfNeeded: bias) + return Int(truncatingIfNeeded: v >= mask ? v - mask : v) + } + + /// Encodes the specified logical value into a `scale`-bit bit pattern suitable + /// for storing into a bucket. + /// + /// The nil value is represented by an all-zero bit pattern. + /// Other values are stored as the complement of their lowest `scale` bits. + /// The range of representable values is `0 ..< bucketCount - 1`. + /// (Note that the value `bucketCount - 1` is missing from this range, as it + /// its encoding is used for `nil`. This isn't an issue, because the maximum + /// load factor guarantees that the hash table will never be completely full.) + @inlinable + func _bucketContents(for value: Int?) -> UInt64 { + guard var value = value else { return 0 } + let mask = Int(truncatingIfNeeded: bucketMask) + assert(value >= 0 && value < mask) + value &-= bias + if value < 0 { value += mask } + assert(value >= 0 && value < mask) + return UInt64(truncatingIfNeeded: value ^ mask) + } + + @inlinable + subscript(word word: Int) -> UInt64 { + @inline(__always) get { + assert(word >= 0 && word < bucketCount) + return _buckets[word] + } + @inline(__always) nonmutating set { + assert(word >= 0 && word < bucketCount) + assertMutable() + _buckets[word] = newValue + } + } + + @inlinable + subscript(raw bucket: Bucket) -> UInt64 { + get { + assert(bucket.offset < bucketCount) + let (word, bit) = position(of: bucket) + var value = self[word: word] &>> bit + let extractedBits = 64 - bit + if extractedBits < scale { + let word2 = self.word(after: word) + value &= (1 &<< extractedBits) - 1 + value |= self[word: word2] &<< extractedBits + } + return value & bucketMask + } + nonmutating set { + assertMutable() + assert(bucket.offset < bucketCount) + let mask = bucketMask + assert(newValue <= mask) + let (word, bit) = position(of: bucket) + self[word: word] &= ~(mask &<< bit) + self[word: word] |= newValue &<< bit + let extractedBits = 64 - bit + if extractedBits < scale { + let word2 = self.word(after: word) + self[word: word2] &= ~((1 &<< (scale - extractedBits)) - 1) + self[word: word2] |= newValue &>> extractedBits + } + } + } + + @inlinable + @inline(__always) + func isOccupied(_ bucket: Bucket) -> Bool { + self[raw: bucket] != 0 + } + + /// Return or update the current value stored in the specified bucket. + /// A nil value indicates that the bucket is empty. + @inlinable + internal subscript(bucket: Bucket) -> Int? { + get { + let contents = self[raw: bucket] + return _value(forBucketContents: contents) + } + nonmutating set { + assertMutable() + let v = _bucketContents(for: newValue) + self[raw: bucket] = v + } + } +} + +extension _UnsafeHashTable { + @inlinable + internal func _find( + _ item: Base.Element, + in elements: Base + ) -> (index: Int?, bucket: Bucket) + where Base.Element: Hashable { + let start = idealBucket(for: item) + var (iterator, value) = startFind(start) + while let index = value { + if elements[_offset: index] == item { + return (index, iterator.currentBucket) + } + value = iterator.findNext() + } + return (nil, iterator.currentBucket) + } +} + +extension _UnsafeHashTable { + @usableFromInline + internal func firstOccupiedBucketInChain(with bucket: Bucket) -> Bucket { + var bucket = bucket + repeat { + bucket = self.bucket(before: bucket) + } while isOccupied(bucket) + return self.bucket(after: bucket) + } + + @inlinable + internal func delete( + bucket: Bucket, + hashValueGenerator: (Int, Int) -> Int // (offset, seed) -> hashValue + ) { + assertMutable() + var it = bucketIterator(startingAt: bucket) + assert(it.isOccupied) + it.advance() + guard it.isOccupied else { + // Fast path: Don't get the start bucket when there's nothing to do. + self[bucket] = nil + return + } + // If we've put a hole in the middle of a collision chain, some element after + // the hole may belong where the new hole is. + + // Find the first bucket in the collision chain that contains the entry we've just deleted. + let start = firstOccupiedBucketInChain(with: bucket) + var hole = bucket + + while it.isOccupied { + let hash = hashValueGenerator(it.currentValue!, seed) + let candidate = idealBucket(forHashValue: hash) + + // Does this element belong between start and hole? We need two + // separate tests depending on whether [start, hole] wraps around the + // end of the storage. + let c0 = candidate.offset >= start.offset + let c1 = candidate.offset <= hole.offset + if start.offset <= hole.offset ? (c0 && c1) : (c0 || c1) { + // Fill the hole. Here we are mutating table contents behind the back of + // the iterator; this is okay since we know we are never going to revisit + // `hole` with it. + self[hole] = it.currentValue + hole = it.currentBucket + } + it.advance() + } + self[hole] = nil + } +} + +extension _UnsafeHashTable { + @inlinable + internal func adjustContents( + preparingForInsertionOfElementAtOffset offset: Int, + in elements: Base + ) where Base.Element: Hashable { + assertMutable() + let index = elements._index(at: offset) + if offset < elements.count / 2 { + self.bias += 1 + if offset <= capacity / 3 { + var i = 1 + for item in elements[..= offset { + it.currentValue = value + 1 + } + it.advance() + } while it.currentBucket.offset != 0 + } + } + } +} + +extension _UnsafeHashTable { + @inlinable + @inline(__always) + internal func adjustContents( + preparingForRemovalOf index: Base.Index, + in elements: Base + ) where Base.Element: Hashable { + let next = elements.index(after: index) + adjustContents(preparingForRemovalOf: index ..< next, in: elements) + } + + @inlinable + internal func adjustContents( + preparingForRemovalOf bounds: Range, + in elements: Base + ) where Base.Element: Hashable { + assertMutable() + let startOffset = elements._offset(of: bounds.lowerBound) + let endOffset = elements._offset(of: bounds.upperBound) + let c = endOffset - startOffset + guard c > 0 else { return } + let remainingCount = elements.count - c + + if startOffset >= remainingCount / 2 { + let tailCount = elements.count - endOffset + if tailCount < capacity / 3 { + var i = endOffset + for item in elements[bounds.upperBound...] { + var it = self.bucketIterator(for: item) + it.advance(until: i) + it.currentValue = i - c + i += 1 + } + } else { + var it = bucketIterator(startingAt: Bucket(offset: 0)) + repeat { + if let value = it.currentValue { + if value >= endOffset { + it.currentValue = value - c + } else { + assert(value < startOffset) + } + } + it.advance() + } while it.currentBucket.offset != 0 + } + } else { + if startOffset < capacity / 3 { + var i = 0 + for item in elements[..= endOffset) + } + } + it.advance() + } while it.currentBucket.offset != 0 + } + self.bias -= c + } + } +} + +extension _UnsafeHashTable { + @usableFromInline + internal func clear() { + assertMutable() + _buckets.assign(repeating: 0, count: wordCount) + } +} + +extension _UnsafeHashTable { + /// Fill an empty hash table by populating it with data from `elements`. + /// + /// - Parameter elements: A random-access collection for which this table is being generated. + @inlinable + internal func fill( + uncheckedUniqueElements elements: C + ) where C.Element: Hashable { + assertMutable() + assert(elements.count <= capacity) + // Iterate over elements and insert their offset into the hash table. + var offset = 0 + for index in elements.indices { + // Find the insertion position. We know that we're inserting a new item, + // so there is no need to compare it with any of the existing ones. + var it = bucketIterator(for: elements[index]) + it.advanceToNextUnoccupiedBucket() + it.currentValue = offset + offset += 1 + } + } + + /// Fill an empty hash table by populating it with data from `elements`. + /// + /// - Parameter elements: A random-access collection for which this table is being generated. + /// - Parameter stoppingOnFirstDuplicateValue: If true, check for duplicate values and stop inserting items when one is found. + /// - Returns: `(success, index)` where `success` is a boolean value indicating that every value in `elements` was successfully inserted. A false success indicates that duplicate elements have been found; in this case `index` points to the first duplicate value; otherwise `index` is set to `elements.endIndex`. + @inlinable + internal func fill( + untilFirstDuplicateIn elements: C + ) -> (success: Bool, end: C.Index) + where C.Element: Hashable { + assertMutable() + assert(elements.count <= capacity) + // Iterate over elements and insert their offset into the hash table. + var offset = 0 + for index in elements.indices { + // Find the insertion position. We know that we're inserting a new item, + // so there is no need to compare it with any of the existing ones. + var it = bucketIterator(for: elements[index]) + while let offset = it.currentValue { + guard elements[_offset: offset] != elements[index] else { + return (false, index) + } + it.advance() + } + it.currentValue = offset + offset += 1 + } + return (true, elements.endIndex) + } +} diff --git a/Sources/PluginCore/OrderedCollections/HashTable/_HashTable.swift b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable.swift new file mode 100644 index 0000000000..319a65ff50 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/HashTable/_HashTable.swift @@ -0,0 +1,202 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +@usableFromInline +@frozen +internal struct _HashTable { + @usableFromInline + internal var _storage: Storage + + @inlinable + @inline(__always) + internal init(_ storage: Storage) { + _storage = storage + } +} + +extension _HashTable { + /// A class holding hash table storage for a `OrderedSet` collection. + /// Values in the hash table are offsets into separate element storage, so + /// this class doesn't need to be generic over `OrderedSet`'s `Element` type. + @usableFromInline + internal final class Storage + : ManagedBuffer + {} +} + +extension _HashTable { + /// Allocate a new empty hash table buffer of the specified scale. + @usableFromInline + @_effects(releasenone) + internal init(scale: Int, reservedScale: Int = 0) { + assert(scale >= Self.minimumScale && scale <= Self.maximumScale) + let wordCount = Self.wordCount(forScale: scale) + let storage = Storage.create( + minimumCapacity: wordCount, + makingHeaderWith: { object in + #if COLLECTIONS_DETERMINISTIC_HASHING + let seed = scale << 6 + #else + let seed = Int(bitPattern: Unmanaged.passUnretained(object).toOpaque()) + #endif + return Header(scale: scale, reservedScale: reservedScale, seed: seed) + }) + storage.withUnsafeMutablePointerToElements { elements in + elements.initialize(repeating: 0, count: wordCount) + } + self.init(unsafeDowncast(storage, to: Storage.self)) + } + + /// Populate a new hash table with data from `elements`. + /// + /// - Parameter scale: The desired hash table scale or nil to use the minimum scale that satisfies invariants. + /// - Parameter reservedScale: The reserved scale to remember in the returned storage. + /// - Parameter duplicates: The strategy to use to handle duplicate items. + /// - Returns: `(storage, index)` where `storage` is a storage instance. The contents of `storage` reflects all elements in `contents[contents.startIndex ..< index]`. `index` is usually `contents.endIndex`, except when the function was asked to reject duplicates, in which case `index` addresses the first duplicate element in `contents` (if any). + @inlinable + @inline(never) + @_effects(releasenone) + static func create( + uncheckedUniqueElements elements: C, + scale: Int? = nil, + reservedScale: Int = 0 + ) -> _HashTable? + where C.Element: Hashable { + let minScale = Self.scale(forCapacity: elements.count) + let scale = Swift.max(Swift.max(scale ?? 0, minScale), + reservedScale) + if scale < Self.minimumScale { return nil } + let hashTable = Self(scale: scale, reservedScale: reservedScale) + hashTable.update { handle in + handle.fill(uncheckedUniqueElements: elements) + } + return hashTable + } + + /// Populate a new hash table with data from `elements`. + /// + /// - Parameter scale: The desired hash table scale or nil to use the minimum scale that satisfies invariants. + /// - Parameter reservedScale: The reserved scale to remember in the returned storage. + /// - Parameter duplicates: The strategy to use to handle duplicate items. + /// - Returns: `(storage, index)` where `storage` is a storage instance. The contents of `storage` reflects all elements in `contents[contents.startIndex ..< index]`. `index` is usually `contents.endIndex`, except when the function was asked to reject duplicates, in which case `index` addresses the first duplicate element in `contents` (if any). + @inlinable + @inline(never) + @_effects(releasenone) + static func create( + untilFirstDuplicateIn elements: C, + scale: Int? = nil, + reservedScale: Int = 0 + ) -> (hashTable: _HashTable?, end: C.Index) + where C.Element: Hashable { + let minScale = Self.scale(forCapacity: elements.count) + let scale = Swift.max(Swift.max(scale ?? 0, minScale), + reservedScale) + if scale < Self.minimumScale { + // Don't hash anything. + if elements.count < 2 { return (nil, elements.endIndex) } + var temp: [C.Element] = [] + temp.reserveCapacity(elements.count) + for i in elements.indices { + let item = elements[i] + guard !temp.contains(item) else { return (nil, i) } + temp.append(item) + } + return (nil, elements.endIndex) + } + let hashTable = Self(scale: scale, reservedScale: reservedScale) + let (_, index) = hashTable.update { handle in + handle.fill(untilFirstDuplicateIn: elements) + } + return (hashTable, index) + } + + /// Create and return a new copy of this instance. The result has the same + /// scale and seed, and contains the exact same bucket data as the original instance. + @usableFromInline + @_effects(releasenone) + internal func copy() -> _HashTable { + self.read { handle in + let wordCount = handle.wordCount + let new = Storage.create( + minimumCapacity: wordCount, + makingHeaderWith: { _ in handle._header.pointee }) + new.withUnsafeMutablePointerToElements { elements in + elements.initialize(from: handle._buckets, count: wordCount) + } + return Self(unsafeDowncast(new, to: Storage.self)) + } + } +} + + + +extension _HashTable { + /// Call `body` with a hash table handle suitable for read-only use. + /// + /// - Warning: The handle supplied to `body` is only valid for the duration of + /// the closure call. The closure must not escape it outside the call. + @inlinable + @inline(__always) + internal func read(_ body: (_UnsafeHashTable) throws -> R) rethrows -> R { + try _storage.withUnsafeMutablePointers { header, elements in + let handle = _UnsafeHashTable(header: header, buckets: elements, readonly: true) + return try body(handle) + } + } + + /// Call `body` with a hash table handle suitable for mutating use. + /// + /// - Warning: The handle supplied to `body` is only valid for the duration of + /// the closure call. The closure must not escape it outside the call. + @inlinable + @inline(__always) + internal func update(_ body: (_UnsafeHashTable) throws -> R) rethrows -> R { + try _storage.withUnsafeMutablePointers { header, elements in + let handle = _UnsafeHashTable(header: header, buckets: elements, readonly: false) + return try body(handle) + } + } +} + +extension _HashTable { + @inlinable + internal var header: Header { + get { _storage.header } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + nonmutating _modify { yield &_storage.header } + } + + @inlinable + internal var capacity: Int { + _storage.header.capacity + } + + @inlinable + internal var minimumCapacity: Int { + if scale == reservedScale { return 0 } + return Self.minimumCapacity(forScale: scale) + } + + @inlinable + internal var scale: Int { + _storage.header.scale + } + + @inlinable + internal var reservedScale: Int { + _storage.header.reservedScale + } + + @inlinable + internal var bias: Int { + _storage.header.bias + } +} diff --git a/Sources/PluginCore/OrderedCollections/HashTable/_Hashtable+Header.swift b/Sources/PluginCore/OrderedCollections/HashTable/_Hashtable+Header.swift new file mode 100644 index 0000000000..93ceb24677 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/HashTable/_Hashtable+Header.swift @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension _HashTable { + /// The storage header for hash table buffers. + /// + /// Note that we don't store the number of items currently in the table; + /// that information can be easily retrieved from the element storage. + @usableFromInline + internal struct Header { + /// We are packing the scale data into the lower bits of the seed & bias + /// to saves a bit of space that would be otherwise taken up by padding. + /// + /// Layout: + /// + /// 63 6 5 0 + /// ├──────────────────────────────────────────────┼────────┤ + /// │ seed │ scale │ + /// └──────────────────────────────────────────────┴────────┘ + /// 63 6 5 0 + /// ├──────────────────────────────────────────────┼────────┤ + /// │ bias │ rsvd │ + /// └──────────────────────────────────────────────┴────────┘ + @usableFromInline + var _scaleAndSeed: UInt64 + @usableFromInline + var _reservedScaleAndBias: UInt64 + + init(scale: Int, reservedScale: Int, seed: Int) { + assert(scale >= _HashTable.minimumScale && scale <= _HashTable.maximumScale) + assert(reservedScale >= 0 && reservedScale <= _HashTable.maximumScale) + _scaleAndSeed = UInt64(truncatingIfNeeded: seed) << (Swift.max(UInt64.bitWidth - Int.bitWidth, 6)) + _scaleAndSeed &= ~0x3F + _scaleAndSeed |= UInt64(truncatingIfNeeded: scale) + _reservedScaleAndBias = UInt64(truncatingIfNeeded: reservedScale) + assert(self.scale == scale) + assert(self.reservedScale == reservedScale) + assert(self.bias == 0) + } + + /// The scale of the hash table. A table of scale *n* holds 2^*n* buckets, + /// each of which contain an *n*-bit value. + @inlinable + @inline(__always) + var scale: Int { Int(_scaleAndSeed & 0x3F) } + + /// The scale corresponding to the last call to `reserveCapacity`. + /// We remember this here to make sure we don't shrink the table below its reserved size. + @inlinable + var reservedScale: Int { + @inline(__always) + get { Int(_reservedScaleAndBias & 0x3F) } + set { + assert(newValue >= 0 && newValue < 64) + _reservedScaleAndBias &= ~0x3F + _reservedScaleAndBias |= UInt64(truncatingIfNeeded: newValue) & 0x3F + } + } + + /// The hasher seed to use within this hash table. + @inlinable + @inline(__always) + var seed: Int { + Int(truncatingIfNeeded: _scaleAndSeed) + } + + /// A bias value that needs to be added to buckets to convert them into offsets + /// into element storage. (This allows O(1) insertions at the front when the + /// underlying storage supports it.) + @inlinable + var bias: Int { + @inline(__always) + get { Int(truncatingIfNeeded: _reservedScaleAndBias) &>> 6 } + set { + let limit = (1 &<< scale) - 1 + var bias = newValue + if bias < 0 { bias += limit } + if bias >= limit { bias -= limit } + assert(bias >= 0 && bias < limit) + _reservedScaleAndBias &= 0x3F + _reservedScaleAndBias |= UInt64(truncatingIfNeeded: bias) &<< 6 + assert(self.bias >= 0 && self.bias < limit) + } + } + + /// The maximum number of items that can fit into this table. + @inlinable + @inline(__always) + var capacity: Int { _HashTable.maximumCapacity(forScale: scale) } + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.Elements.md b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.Elements.md new file mode 100644 index 0000000000..752f3f5c03 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.Elements.md @@ -0,0 +1,37 @@ +# ``OrderedCollections/OrderedDictionary/Elements-swift.struct`` + +## Topics + +### Inspecting an Elements View + +- ``isEmpty`` +- ``count`` + +### Accessing Elements + +- ``subscript(_:)-4xwc2`` +- ``keys`` +- ``values`` +- ``index(forKey:)`` + +### Removing Elements + +- ``remove(at:)`` +- ``removeSubrange(_:)-5x7oo`` +- ``removeSubrange(_:)-7wdak`` +- ``removeAll(keepingCapacity:)`` +- ``removeAll(where:)`` +- ``removeFirst()`` +- ``removeFirst(_:)`` +- ``removeLast()`` +- ``removeLast(_:)`` + +### Reordering Elements + +- ``swapAt(_:_:)`` +- ``reverse()`` +- ``sort()`` +- ``sort(by:)`` +- ``partition(by:)`` +- ``shuffle()`` +- ``shuffle(using:)`` diff --git a/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.Values.md b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.Values.md new file mode 100644 index 0000000000..6e50c1f4f5 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.Values.md @@ -0,0 +1,24 @@ +# ``OrderedCollections/OrderedDictionary/Values-swift.struct`` + +## Topics + +### Inspecting a Values Collection + +- ``isEmpty`` +- ``count`` + +### Accessing Elements + +- ``subscript(_:)-25vfz`` +- ``elements`` +- ``withUnsafeBufferPointer(_:)`` +- ``withUnsafeMutableBufferPointer(_:)`` + +### Reordering Elements + +- ``swapAt(_:_:)-77eiy`` +- ``partition(by:)-9x0i5`` +- ``sort()`` +- ``sort(by:)`` +- ``shuffle()`` +- ``shuffle(using:)`` diff --git a/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.md b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.md new file mode 100644 index 0000000000..7e0845ee62 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedDictionary.md @@ -0,0 +1,94 @@ +# ``OrderedCollections/OrderedDictionary`` + +## Topics + +### Creating a Dictionary + +- ``init()`` +- ``init(minimumCapacity:persistent:)`` +- ``init(uniqueKeysWithValues:)-5ux9r`` +- ``init(uniqueKeysWithValues:)-88mzi`` +- ``init(uncheckedUniqueKeysWithValues:)-6gxhj`` +- ``init(uncheckedUniqueKeysWithValues:)-2j0dw`` +- ``init(uncheckedUniqueKeysWithValues:)-6gxhj`` +- ``init(uniqueKeys:values:)`` +- ``init(uncheckedUniqueKeys:values:)`` +- ``init(_:uniquingKeysWith:)-2y39b`` +- ``init(_:uniquingKeysWith:)-zhfp`` +- ``init(grouping:by:)-6mahw`` +- ``init(grouping:by:)-6m2zw`` + +### Inspecting a Dictionary + +- ``isEmpty`` +- ``count`` + +### Accessing Keys and Values + +- ``subscript(_:)`` +- ``subscript(_:default:)`` +- ``index(forKey:)`` + +### Collection Views + +- ``keys`` +- ``values-swift.property`` +- ``elements-swift.property`` + +### Updating Values + +- ``updateValue(_:forKey:)`` +- ``updateValue(_:forKey:insertingAt:)`` +- ``updateValue(forKey:default:with:)`` +- ``updateValue(forKey:insertingDefault:at:with:)`` + +### Removing Keys and Values + +- ``removeValue(forKey:)`` +- ``remove(at:)`` +- ``filter(_:)`` +- ``removeAll(where:)`` +- ``removeAll(keepingCapacity:)`` +- ``removeFirst()`` +- ``removeLast()`` +- ``removeFirst(_:)`` +- ``removeLast(_:)`` +- ``removeSubrange(_:)-512n3`` +- ``removeSubrange(_:)-8rmzx`` + +### Combining Dictionaries + +- ``merge(_:uniquingKeysWith:)-6ka2i`` +- ``merge(_:uniquingKeysWith:)-9wkad`` +- ``merging(_:uniquingKeysWith:)-4z49c`` +- ``merging(_:uniquingKeysWith:)-2e0xa`` + +### Comparing Dictionaries + +- ``==(_:_:)`` + +### Reordering Elements + +- ``swapAt(_:_:)`` +- ``reverse()`` +- ``sort()`` +- ``sort(by:)`` +- ``reverse()`` +- ``shuffle()`` +- ``shuffle(using:)`` +- ``partition(by:)`` + +### Transforming a Dictionary + +- ``mapValues(_:)`` +- ``compactMapValues(_:)`` + +### Memory Management + +- ``reserveCapacity(_:)`` + +### Supporting Types + +- ``Index`` +- ``Values-swift.struct`` +- ``Elements-swift.struct`` diff --git a/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedSet.UnorderedView.md b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedSet.UnorderedView.md new file mode 100644 index 0000000000..ebc2c88921 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedSet.UnorderedView.md @@ -0,0 +1,53 @@ +# ``OrderedCollections/OrderedSet/UnorderedView`` + +## Topics + +### Binary Set Operations + +- ``intersection(_:)-3q45l`` +- ``intersection(_:)-6ee3o`` + +- ``union(_:)-79uk3`` +- ``union(_:)-23dm1`` + +- ``subtracting(_:)-3ct1b`` +- ``subtracting(_:)-8e6mw`` + +- ``symmetricDifference(_:)-6aed7`` +- ``symmetricDifference(_:)-7r79p`` + +- ``formIntersection(_:)-4ow38`` +- ``formIntersection(_:)-80iht`` + +- ``formUnion(_:)-6ijb`` +- ``formUnion(_:)-8tuol`` + +- ``subtract(_:)-627eq`` +- ``subtract(_:)-4pjhu`` + +- ``formSymmetricDifference(_:)-8pkt5`` +- ``formSymmetricDifference(_:)-75z52`` + +### Binary Set Predicates + +- ``==(_:_:)`` + +- ``isSubset(of:)-2dx31`` +- ``isSubset(of:)-801lo`` +- ``isSubset(of:)-952h5`` + +- ``isSuperset(of:)-9t33p`` +- ``isSuperset(of:)-2vtig`` +- ``isSuperset(of:)-9krpz`` + +- ``isStrictSubset(of:)-9o6mg`` +- ``isStrictSubset(of:)-91par`` +- ``isStrictSubset(of:)-7n66e`` + +- ``isStrictSuperset(of:)-89ig3`` +- ``isStrictSuperset(of:)-1e0xt`` +- ``isStrictSuperset(of:)-5dsfd`` + +- ``isDisjoint(with:)-3wuso`` +- ``isDisjoint(with:)-25vmx`` +- ``isDisjoint(with:)-8nfqs`` diff --git a/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedSet.md b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedSet.md new file mode 100644 index 0000000000..eac28040b8 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/Extensions/OrderedSet.md @@ -0,0 +1,133 @@ +# ``OrderedCollections/OrderedSet`` + +## Topics + +### Creating a Set + +- ``init()`` +- ``init(_:)-5zktd`` +- ``init(_:)-3d7qr`` +- ``init(_:)-68j7`` +- ``init(_:)-8zm9d`` +- ``init(_:)-7rt2h`` +- ``init(_:)-8tli8`` +- ``init(_:)-2d3a9`` +- ``init(uncheckedUniqueElements:)`` +- ``init(minimumCapacity:persistent:)`` + +### Collection Views + +- ``UnorderedView`` +- ``unordered`` +- ``elements`` + +### Finding Elements + +- ``contains(_:)`` +- ``firstIndex(of:)`` +- ``lastIndex(of:)`` + +### Adding and Updating Elements + +- ``append(_:)`` +- ``insert(_:at:)`` +- ``append(contentsOf:)`` +- ``updateOrAppend(_:)`` +- ``updateOrInsert(_:at:)`` +- ``update(_:at:)`` + +### Removing Elements + +- ``filter(_:)`` +- ``removeAll(where:)`` +- ``remove(_:)`` +- ``remove(at:)`` +- ``removeAll(keepingCapacity:)`` +- ``removeFirst()`` +- ``removeLast()`` +- ``removeFirst(_:)`` +- ``removeLast(_:)`` +- ``removeSubrange(_:)-62u6a`` +- ``removeSubrange(_:)-2fqke`` + +### Combining Sets + +- ``intersection(_:)-4o09a`` +- ``intersection(_:)-9yzg3`` +- ``intersection(_:)-80md4`` + +- ``union(_:)-67y2h`` +- ``union(_:)-3lt5i`` +- ``union(_:)-2939h`` + +- ``subtracting(_:)-5graf`` +- ``subtracting(_:)-7kl8r`` +- ``subtracting(_:)-1gl4y`` + +- ``symmetricDifference(_:)-1810l`` +- ``symmetricDifference(_:)-8dvm6`` +- ``symmetricDifference(_:)-9huk7`` + +- ``formIntersection(_:)-43o1u`` +- ``formIntersection(_:)-2a4y4`` +- ``formIntersection(_:)-7odn2`` + +- ``formUnion(_:)-6pksr`` +- ``formUnion(_:)-3dkzw`` +- ``formUnion(_:)-59end`` + +- ``subtract(_:)-3b6nj`` +- ``subtract(_:)-9rtmd`` +- ``subtract(_:)-9wmg8`` + +- ``formSymmetricDifference(_:)-96csi`` +- ``formSymmetricDifference(_:)-2ll2z`` +- ``formSymmetricDifference(_:)-391sm`` + +### Comparing Sets + +- ``==(_:_:)`` + +- ``isSubset(of:)-ptij`` +- ``isSubset(of:)-3mw6r`` +- ``isSubset(of:)-8yb29`` +- ``isSubset(of:)-9hxl4`` + +- ``isSuperset(of:)-4rrsh`` +- ``isSuperset(of:)-2bbv8`` +- ``isSuperset(of:)-7xvog`` +- ``isSuperset(of:)-7oow7`` + +- ``isStrictSubset(of:)-8m21h`` +- ``isStrictSubset(of:)-9lv3x`` +- ``isStrictSubset(of:)-4efhn`` +- ``isStrictSubset(of:)-10abw`` + +- ``isStrictSuperset(of:)-7u97x`` +- ``isStrictSuperset(of:)-3kfwa`` +- ``isStrictSuperset(of:)-98d9s`` +- ``isStrictSuperset(of:)-5e6d5`` + +- ``isDisjoint(with:)-6vmoh`` +- ``isDisjoint(with:)-4tsmx`` +- ``isDisjoint(with:)-54iy6`` +- ``isDisjoint(with:)-7nqur`` + +### Reordering Elements + +- ``swapAt(_:_:)`` +- ``sort()`` +- ``sort(by:)`` +- ``reverse()`` +- ``shuffle()`` +- ``shuffle(using:)`` +- ``partition(by:)`` + +### Creating and Applying Differences + +- ``difference(from:)-30bkk`` +- ``applying(_:)`` + +### Memory Management + +- ``reserveCapacity(_:)`` diff --git a/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/OrderedCollections.md b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/OrderedCollections.md new file mode 100644 index 0000000000..dcc7288a00 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedCollections.docc/OrderedCollections.md @@ -0,0 +1,10 @@ +# ``OrderedCollections`` + +The `OrderedCollections` module provides hashed collection types that work like the standard `Set` and `Dictionary`, but maintain their elements in a particular user-specified order. + +## Topics + +### Structures + +- ``OrderedSet`` +- ``OrderedDictionary`` diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Codable.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Codable.swift new file mode 100644 index 0000000000..61a9808cf0 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Codable.swift @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary: Encodable where Key: Encodable, Value: Encodable { + /// Encodes the contents of this dictionary into the given encoder. + /// + /// The dictionary's contents are encoded as alternating key-value pairs in + /// an unkeyed container. + /// + /// This function throws an error if any values are invalid for the given + /// encoder's format. + /// + /// - Note: Unlike the standard `Dictionary` type, ordered dictionaries + /// always encode themselves into an unkeyed container, because + /// `Codable`'s keyed containers do not guarantee that they preserve the + /// ordering of the items they contain. (And in popular encoding formats, + /// keyed containers tend to map to unordered data structures -- e.g., + /// JSON's "object" construct is explicitly unordered.) + /// + /// - Parameter encoder: The encoder to write data to. + @inlinable + public func encode(to encoder: Encoder) throws { + // Encode contents as an array of alternating key-value pairs. + var container = encoder.unkeyedContainer() + for (key, value) in self { + try container.encode(key) + try container.encode(value) + } + } +} + +extension OrderedDictionary: Decodable where Key: Decodable, Value: Decodable { + /// Creates a new dictionary by decoding from the given decoder. + /// + /// `OrderedDictionary` expects its contents to be encoded as alternating + /// key-value pairs in an unkeyed container. + /// + /// This initializer throws an error if reading from the decoder fails, or + /// if the decoded contents are not in the expected format. + /// + /// - Note: Unlike the standard `Dictionary` type, ordered dictionaries + /// always encode themselves into an unkeyed container, because + /// `Codable`'s keyed containers do not guarantee that they preserve the + /// ordering of the items they contain. (And in popular encoding formats, + /// keyed containers tend to map to unordered data structures -- e.g., + /// JSON's "object" construct is explicitly unordered.) + /// + /// - Parameter decoder: The decoder to read data from. + @inlinable + public init(from decoder: Decoder) throws { + // We expect to be encoded as an array of alternating key-value pairs. + var container = try decoder.unkeyedContainer() + + self.init() + while !container.isAtEnd { + let key = try container.decode(Key.self) + let (index, bucket) = self._keys._find(key) + guard index == nil else { + let context = DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Duplicate key at offset \(container.currentIndex - 1)") + throw DecodingError.dataCorrupted(context) + } + + guard !container.isAtEnd else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unkeyed container reached end before value in key-value pair" + ) + ) + } + let value = try container.decode(Value.self) + _keys._appendNew(key, in: bucket) + _values.append(value) + } + _checkInvariants() + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomDebugStringConvertible.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomDebugStringConvertible.swift new file mode 100644 index 0000000000..4bc122a307 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomDebugStringConvertible.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary: CustomDebugStringConvertible { + /// A textual representation of this instance, suitable for debugging. + public var debugDescription: String { + _debugDescription(typeName: _debugTypeName()) + } + + internal func _debugTypeName() -> String { + "OrderedDictionary<\(Key.self), \(Value.self)>" + } + + internal func _debugDescription(typeName: String) -> String { + var result = "\(typeName)(" + if isEmpty { + result += "[:]" + } else { + result += "[" + var first = true + for (key, value) in self { + if first { + first = false + } else { + result += ", " + } + debugPrint(key, terminator: "", to: &result) + result += ": " + debugPrint(value, terminator: "", to: &result) + } + result += "]" + } + result += ")" + return result + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomReflectable.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomReflectable.swift new file mode 100644 index 0000000000..82a5d37cf7 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomReflectable.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary: CustomReflectable { + /// The custom mirror for this instance. + public var customMirror: Mirror { + Mirror(self, unlabeledChildren: self.elements, displayStyle: .dictionary) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomStringConvertible.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomStringConvertible.swift new file mode 100644 index 0000000000..eb40ebab10 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+CustomStringConvertible.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary: CustomStringConvertible { + /// A textual representation of this instance. + public var description: String { + if isEmpty { return "[:]" } + var result = "[" + var first = true + for (key, value) in self { + if first { + first = false + } else { + result += ", " + } + result += "\(key): \(value)" + } + result += "]" + return result + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Deprecations.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Deprecations.swift new file mode 100644 index 0000000000..a8e531d8e9 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Deprecations.swift @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary { + /// Accesses the element at the specified index. + /// + /// - Parameter offset: The offset of the element to access, measured from + /// the start of the collection. `offset` must be greater than or equal to + /// `0` and less than `count`. + /// + /// - Complexity: O(1) + @available(*, unavailable, // deprecated in 0.0.6, obsoleted in 1.0.0 + message: "Please use `elements[offset]`") + @inlinable + @inline(__always) + public subscript(offset offset: Int) -> Element { + fatalError() + } +} + +extension OrderedDictionary { + @available(*, unavailable, // deprecated in 0.0.6, obsoleted in 1.0.0 + renamed: "updateValue(forKey:default:with:)") + @inlinable + public mutating func modifyValue( + forKey key: Key, + default defaultValue: @autoclosure () -> Value, + _ body: (inout Value) throws -> R + ) rethrows -> R { + fatalError() + } + + @available(*, unavailable, // deprecated in 0.0.6, obsoleted in 1.0.0 + renamed: "updateValue(forKey:insertingDefault:at:with:)") + @inlinable + public mutating func modifyValue( + forKey key: Key, + insertingDefault defaultValue: @autoclosure () -> Value, + at index: Int, + _ body: (inout Value) throws -> R + ) rethrows -> R { + fatalError() + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements+SubSequence.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements+SubSequence.swift new file mode 100644 index 0000000000..ee7ccc1e4d --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements+SubSequence.swift @@ -0,0 +1,330 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary.Elements { + /// A collection that represents a contiguous slice of an ordered dictionary. + /// + /// Ordered dictionary slices are random access collections that + /// support efficient key-based lookups. + @frozen + public struct SubSequence { + @usableFromInline + internal var _base: OrderedDictionary + @usableFromInline + internal var _bounds: Range + + @inlinable + @inline(__always) + internal init(_base: OrderedDictionary, bounds: Range) { + self._base = _base + self._bounds = bounds + } + } +} + +extension OrderedDictionary.Elements.SubSequence { + /// A read-only collection view containing the keys in this slice. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var keys: OrderedSet.SubSequence { + _base._keys[_bounds] + } + + /// A read-only collection view containing the values in this slice. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var values: OrderedDictionary.Values.SubSequence { + _base.values[_bounds] + } +} + +extension OrderedDictionary.Elements.SubSequence { + /// Returns the index for the given key. + /// + /// If the given key is found in the dictionary slice, this method returns an + /// index into the dictionary that corresponds with the key-value pair. + /// + /// let countryCodes: OrderedDictionary = ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"] + /// let slice = countryCodes.elements[1...] + /// let index = slice.index(forKey: "JP") + /// + /// print("Country code for \(countryCodes[offset: index!].value): '\(countryCodes[offset: index!].key)'.") + /// // Prints "Country code for Japan: 'JP'." + /// + /// - Parameter key: The key to find in the dictionary slice. + /// + /// - Returns: The index for `key` and its associated value if `key` is in + /// the dictionary slice; otherwise, `nil`. + /// + /// - Complexity: Expected to be O(1) on average, if `Key` implements + /// high-quality hashing. + @inlinable + public func index(forKey key: Key) -> Int? { + guard let index = _base.index(forKey: key) else { return nil } + guard _bounds.contains(index) else { return nil } + return index + } +} + +extension OrderedDictionary.Elements.SubSequence: Sequence { + // A type representing the collection’s elements. + public typealias Element = OrderedDictionary.Element + + /// The type that allows iteration over the collection's elements. + @frozen + public struct Iterator: IteratorProtocol { + @usableFromInline + internal var _base: OrderedDictionary + + @usableFromInline + internal var _end: Int + + @usableFromInline + internal var _index: Int + + @inlinable + @inline(__always) + internal init(_base: OrderedDictionary.Elements.SubSequence) { + self._base = _base._base + self._end = _base._bounds.upperBound + self._index = _base._bounds.lowerBound + } + + /// Advances to the next element and returns it, or `nil` if no next + /// element exists. + /// + /// - Complexity: O(1) + @inlinable + public mutating func next() -> Element? { + guard _index < _end else { return nil } + defer { _index += 1 } + return (_base._keys[_index], _base._values[_index]) + } + } + + /// Returns an iterator over the elements of this dictionary slice. + @inlinable + @inline(__always) + public func makeIterator() -> Iterator { + Iterator(_base: self) + } +} + +extension OrderedDictionary.Elements.SubSequence: RandomAccessCollection { + /// The index type for an ordered dictionary: `Int`. + /// + /// The indices are integer offsets from the start of the original + /// (unsliced) collection. + public typealias Index = Int + + /// The type that represents the indices that are valid for subscripting an + /// ordered dictionary, in ascending order. + public typealias Indices = Range + + /// Ordered dictionary subsequences are self-slicing. + public typealias SubSequence = Self + + /// The position of the first element in a nonempty ordered dictionary slice. + /// + /// Note that instances of `OrderedDictionary.SubSequence` generally + /// don't have a `startIndex` with an offset of zero. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { _bounds.lowerBound } + + /// The "past the end" position---that is, the position one greater + /// than the last valid subscript argument. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { _bounds.upperBound } + + /// The indices that are valid for subscripting the collection, + /// in ascending order. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var indices: Range { _bounds } + + /// Returns the position immediately after the given index. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the dictionary. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { i + 1 } + + /// Returns the position immediately before the given index. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the dictionary. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { i - 1 } + + /// Replaces the given index with its successor. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the dictionary. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { i += 1 } + + /// Replaces the given index with its predecessor. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the dictionary. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { i -= 1 } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, or the returned value will not be a valid index. + /// + /// - Parameters: + /// - i: A valid index of the dictionary. + /// - distance: The distance to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls to + /// `index(after:)`. If `distance` is negative, this is the same value as + /// the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, unless the index passed as `limit` prevents offsetting + /// beyond those bounds. (Otherwise the returned value won't be a valid index + /// in the set.) + /// + /// - Parameters: + /// - i: A valid index of the dictionary. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the collection to use as a limit. If + /// `distance > 0`, `limit` has no effect if it is less than `i`. + /// Likewise, if `distance < 0`, `limit` has no effect if it is greater + /// than `i`. + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + _base._values.index(i, offsetBy: distance, limitedBy: limit) + } + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. If `end` is equal to + /// `start`, the result is zero. + /// + /// - Returns: The distance between `start` and `end`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + end - start + } + + /// Accesses the element at the specified position. + /// + /// - Parameter index: The position of the element to access. `index` must be + /// greater than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + public subscript(position: Int) -> Element { + precondition(_bounds.contains(position), "Index out of range") + return (_base._keys[position], _base._values[position]) + } + + /// Accesses a contiguous subrange of the dictionary's elements. + /// + /// The returned `Subsequence` instance uses the same indices for the same + /// elements as the original dictionary. In particular, that slice, unlike an + /// `OrderedDictionary`, may have a nonzero `startIndex.offset` and an + /// `endIndex.offset` that is not equal to `count`. Always use the slice's + /// `startIndex` and `endIndex` properties instead of assuming that its + /// indices start or end at a particular value. + /// + /// - Parameter bounds: A range of valid indices in the dictionary. + /// + /// - Complexity: O(1) + @inlinable + public subscript(bounds: Range) -> SubSequence { + precondition( + bounds.lowerBound >= _bounds.lowerBound + && bounds.upperBound <= _bounds.upperBound, + "Index out of range") + return Self(_base: _base, bounds: bounds) + } + + /// A Boolean value indicating whether the collection is empty. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var isEmpty: Bool { _bounds.isEmpty } + + /// The number of elements in the dictionary. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _bounds.count } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift new file mode 100644 index 0000000000..d166763041 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Elements.swift @@ -0,0 +1,653 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary { + /// A view of the contents of an ordered dictionary as a random-access + /// collection. + @frozen + public struct Elements { + @usableFromInline + internal var _base: OrderedDictionary + + @inlinable + @inline(__always) + internal init(_base: OrderedDictionary) { + self._base = _base + } + } +} + +extension OrderedDictionary { + /// A view of the contents of this dictionary as a random-access collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var elements: Elements { + get { + Elements(_base: self) + } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + var elements = Elements(_base: self) + self = Self() + defer { self = elements._base } + yield &elements + } + } +} + +extension OrderedDictionary.Elements { + /// A read-only collection view containing the keys in this collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var keys: OrderedSet { + _base._keys + } + + /// A mutable collection view containing the values in this collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var values: OrderedDictionary.Values { + get { + _base.values + } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + var values = OrderedDictionary.Values(_base: _base) + self = Self(_base: .init()) + defer { self._base = values._base } + yield &values + } + } +} + +extension OrderedDictionary.Elements { + /// Returns the index for the given key. + /// + /// If the given key is found in the dictionary, this method returns an index + /// into the dictionary that corresponds with the key-value pair. + /// + /// let countryCodes: OrderedDictionary = ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"] + /// let index = countryCodes.elements.index(forKey: "JP") + /// + /// print("Country code for \(countryCodes[offset: index!].value): '\(countryCodes[offset: index!].key)'.") + /// // Prints "Country code for Japan: 'JP'." + /// + /// - Parameter key: The key to find in the dictionary. + /// + /// - Returns: The index for `key` and its associated value if `key` is in + /// the dictionary; otherwise, `nil`. + /// + /// - Complexity: Expected to be O(1) on average, if `Key` implements + /// high-quality hashing. + @inlinable + public func index(forKey key: Key) -> Int? { + _base.index(forKey: key) + } +} + +extension OrderedDictionary.Elements: Sequence { + /// The element type of the collection. + public typealias Element = (key: Key, value: Value) + + @inlinable + public var underestimatedCount: Int { _base.count } + + @inlinable + public func makeIterator() -> OrderedDictionary.Iterator { + _base.makeIterator() + } +} + +extension OrderedDictionary.Elements: RandomAccessCollection { + /// The index type for an ordered dictionary: `Int`. + /// + /// Indices in `Elements` are integer offsets from the start of the + /// collection. + public typealias Index = Int + + /// The type that represents the indices that are valid for subscripting the + /// `Elements` collection, in ascending order. + public typealias Indices = Range + + /// The position of the first element in a nonempty dictionary. + /// + /// For an instance of `OrderedDictionary.Elements`, `startIndex` is always + /// zero. If the dictionary is empty, `startIndex` is equal to `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { 0 } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// + /// In `OrderedDictionary.Elements`, `endIndex` always equals the count of + /// elements. If the dictionary is empty, `endIndex` is equal to `startIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { _base.count } + + /// Returns the position immediately after the given index. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { i + 1 } + + /// Returns the position immediately before the given index. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { i - 1 } + + /// Replaces the given index with its successor. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { i += 1 } + + /// Replaces the given index with its predecessor. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { i -= 1 } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, or the returned value will not be a valid index. + /// + /// - Parameters: + /// - i: A valid index of the collection. + /// - distance: The distance to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls to + /// `index(after:)`. If `distance` is negative, this is the same value as + /// the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, unless the index passed as `limit` prevents offsetting + /// beyond those bounds. (Otherwise the returned value won't be a valid index + /// in the collection.) + /// + /// - Parameters: + /// - i: A valid index of the collection. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the collection to use as a limit. If + /// `distance > 0`, `limit` has no effect if it is less than `i`. + /// Likewise, if `distance < 0`, `limit` has no effect if it is greater + /// than `i`. + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + _base._values.index(i, offsetBy: distance, limitedBy: limit) + } + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. If `end` is equal to + /// `start`, the result is zero. + /// + /// - Returns: The distance between `start` and `end`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + end - start + } + + /// Accesses the element at the specified position. + /// + /// - Parameter index: The position of the element to access. `index` must be + /// greater than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public subscript(position: Int) -> Element { + (_base._keys[position], _base._values[position]) + } + + /// Accesses a contiguous subrange of the dictionary's elements. + /// + /// The returned `Subsequence` instance uses the same indices for the same + /// elements as the original collection. In particular, the slice, unlike an + /// `Elements`, may have a nonzero `startIndex` and an `endIndex` that is not + /// equal to `count`. Always use the slice's `startIndex` and `endIndex` + /// properties instead of assuming that its indices start or end at a + /// particular value. + /// + /// - Parameter bounds: A range of valid indices in the collection. + /// + /// - Complexity: O(1) + public subscript(bounds: Range) -> SubSequence { + _failEarlyRangeCheck(bounds, bounds: startIndex ..< endIndex) + return SubSequence(_base: _base, bounds: bounds) + } + + /// A Boolean value indicating whether the collection is empty. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var isEmpty: Bool { _base.isEmpty } + + /// The number of elements in the dictionary. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _base.count } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ index: Int, bounds: Range) { + _base._values._failEarlyRangeCheck(index, bounds: bounds) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ index: Int, bounds: ClosedRange) { + _base._values._failEarlyRangeCheck(index, bounds: bounds) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ range: Range, bounds: Range) { + _base._values._failEarlyRangeCheck(range, bounds: bounds) + } +} + +extension OrderedDictionary.Elements: CustomStringConvertible { + public var description: String { + _base.description + } +} + +extension OrderedDictionary.Elements: CustomDebugStringConvertible { + public var debugDescription: String { + _base._debugDescription( + typeName: "OrderedDictionary<\(Key.self), \(Value.self)>.Elements") + } +} + +extension OrderedDictionary.Elements: CustomReflectable { + public var customMirror: Mirror { + Mirror(self, unlabeledChildren: self, displayStyle: .collection) + } +} + +extension OrderedDictionary.Elements: Equatable where Value: Equatable { + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + left._base == right._base + } +} + +extension OrderedDictionary.Elements: Hashable where Value: Hashable { + @inlinable + public func hash(into hasher: inout Hasher) { + _base.hash(into: &hasher) + } +} + +// MARK: Partial `MutableCollection` + +extension OrderedDictionary.Elements { + /// Exchanges the key-value pairs at the specified indices of the dictionary. + /// + /// Both parameters must be valid indices below `endIndex`. Passing the same + /// index as both `i` and `j` has no effect. + /// + /// - Parameters: + /// - i: The index of the first value to swap. + /// - j: The index of the second value to swap. + /// + /// - Complexity: O(1) when the dictionary's storage isn't shared with another + /// value; O(`count`) otherwise. + @inlinable + @inline(__always) + public mutating func swapAt(_ i: Int, _ j: Int) { + _base.swapAt(i, j) + } + + /// Reorders the elements of the dictionary such that all the elements that + /// match the given predicate are after all the elements that don't match. + /// + /// After partitioning a collection, there is a pivot index `p` where + /// no element before `p` satisfies the `belongsInSecondPartition` + /// predicate and every element at or after `p` satisfies + /// `belongsInSecondPartition`. + /// + /// - Parameter belongsInSecondPartition: A predicate used to partition + /// the collection. All elements satisfying this predicate are ordered + /// after all elements not satisfying it. + /// - Returns: The index of the first element in the reordered collection + /// that matches `belongsInSecondPartition`. If no elements in the + /// collection match `belongsInSecondPartition`, the returned index is + /// equal to the collection's `endIndex`. + /// + /// - Complexity: O(`count`) + @inlinable + @inline(__always) + public mutating func partition( + by belongsInSecondPartition: (Element) throws -> Bool + ) rethrows -> Int { + try _base.partition(by: belongsInSecondPartition) + } +} + +extension OrderedDictionary.Elements { + /// Sorts the collection in place, using the given predicate as the + /// comparison between elements. + /// + /// When you want to sort a collection of elements that don't conform to + /// the `Comparable` protocol, pass a closure to this method that returns + /// `true` when the first element should be ordered before the second. + /// + /// Alternatively, use this method to sort a collection of elements that do + /// conform to `Comparable` when you want the sort to be descending instead + /// of ascending. Pass the greater-than operator (`>`) operator as the + /// predicate. + /// + /// `areInIncreasingOrder` must be a *strict weak ordering* over the + /// elements. That is, for any elements `a`, `b`, and `c`, the following + /// conditions must hold: + /// + /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) + /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are + /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. + /// (Transitive comparability) + /// - Two elements are *incomparable* if neither is ordered before the other + /// according to the predicate. If `a` and `b` are incomparable, and `b` + /// and `c` are incomparable, then `a` and `c` are also incomparable. + /// (Transitive incomparability) + /// + /// The sorting algorithm is guaranteed to be stable. A stable sort + /// preserves the relative order of elements for which + /// `areInIncreasingOrder` does not establish an order. + /// + /// - Parameter areInIncreasingOrder: A predicate that returns `true` if its + /// first argument should be ordered before its second argument; + /// otherwise, `false`. If `areInIncreasingOrder` throws an error during + /// the sort, the elements may be in a different order, but none will be + /// lost. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + @inline(__always) + public mutating func sort( + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows { + try _base.sort(by: areInIncreasingOrder) + } +} + +extension OrderedDictionary.Elements where Key: Comparable { + /// Sorts the dictionary in place. + /// + /// You can sort an ordered dictionary of keys that conform to the + /// `Comparable` protocol by calling this method. The key-value pairs are + /// sorted in ascending order. (`Value` doesn't need to conform to + /// `Comparable` because the keys are guaranteed to be unique.) + /// + /// The sorting algorithm is guaranteed to be stable. A stable sort + /// preserves the relative order of elements that compare as equal. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + @inline(__always) + public mutating func sort() { + _base.sort() + } +} + +extension OrderedDictionary.Elements { + /// Shuffles the collection in place. + /// + /// Use the `shuffle()` method to randomly reorder the elements of an ordered + /// dictionary. + /// + /// This method is equivalent to calling `shuffle(using:)`, passing in the + /// system's default random generator. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @inlinable + public mutating func shuffle() { + _base.shuffle() + } + + /// Shuffles the collection in place, using the given generator as a source + /// for randomness. + /// + /// You use this method to randomize the elements of a collection when you + /// are using a custom random number generator. For example, you can use the + /// `shuffle(using:)` method to randomly reorder the elements of an array. + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the collection. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + /// + /// - Note: The algorithm used to shuffle a collection may change in a future + /// version of Swift. If you're passing a generator that results in the + /// same shuffled order each time you run your program, that sequence may + /// change when your program is compiled using a different version of + /// Swift. + @inlinable + public mutating func shuffle( + using generator: inout T + ) { + _base.shuffle(using: &generator) + } +} + +extension OrderedDictionary.Elements { + /// Reverses the elements of the ordered dictionary in place. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func reverse() { + _base.reverse() + } +} + +// MARK: Partial `RangeReplaceableCollection` + +extension OrderedDictionary.Elements { + /// Removes all members from the dictionary. + /// + /// - Parameter keepingCapacity: If `true`, the dictionary's storage capacity + /// is preserved; if `false`, the underlying storage is released. The + /// default is `false`. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { + _base.removeAll(keepingCapacity: keepCapacity) + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// resulting gap. + /// + /// - Parameter index: The position of the element to remove. `index` must be + /// a valid index of the collection that is not equal to the collection's + /// end index. + /// + /// - Returns: The removed element. + /// + /// - Complexity: O(`count`) + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + _base.remove(at: index) + } + + /// Removes the specified subrange of elements from the collection. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _base.removeSubrange(bounds) + } + + /// Removes the specified subrange of elements from the collection. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange( + _ bounds: R + ) where R.Bound == Int { + _base.removeSubrange(bounds) + } + + + /// Removes the last element of a non-empty dictionary. + /// + /// - Complexity: Expected to be O(`1`) on average, if `Element` implements + /// high-quality hashing. + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + _base.removeLast() + } + + /// Removes the last `n` element of the dictionary. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the collection. + /// + /// - Complexity: Expected to be O(`n`) on average, if `Element` implements + /// high-quality hashing. + @inlinable + public mutating func removeLast(_ n: Int) { + _base.removeLast(n) + } + + /// Removes the first element of a non-empty dictionary. + /// + /// The members following the removed key-value pair need to be moved to close + /// the resulting gaps in the storage arrays. + /// + /// - Complexity: O(`count`). + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + _base.removeFirst() + } + + /// Removes the first `n` elements of the dictionary. + /// + /// The members following the removed items need to be moved to close the + /// resulting gaps in the storage arrays. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the set. + /// + /// - Complexity: O(`count`). + @inlinable + public mutating func removeFirst(_ n: Int) { + _base.removeFirst(n) + } + + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a collection that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// dictionary as its argument and returns a Boolean value indicating + /// whether the element should be removed from the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll( + where shouldBeRemoved: (Self.Element) throws -> Bool + ) rethrows { + try _base.removeAll(where: shouldBeRemoved) + } +} + diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Equatable.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Equatable.swift new file mode 100644 index 0000000000..93ed08c63a --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Equatable.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary: Equatable where Value: Equatable { + /// Returns a Boolean value indicating whether two values are equal. + /// + /// Two ordered dictionaries are considered equal if they contain the same + /// key-value pairs, in the same order. + /// + /// - Complexity: O(`min(left.count, right.count)`) + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + left._keys == right._keys && left._values == right._values + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+ExpressibleByDictionaryLiteral.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+ExpressibleByDictionaryLiteral.swift new file mode 100644 index 0000000000..8c1ed03969 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+ExpressibleByDictionaryLiteral.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary: ExpressibleByDictionaryLiteral { + /// Creates a new ordered dictionary from the contents of a dictionary + /// literal. + /// + /// Do not call this initializer directly. It is used by the compiler when you + /// use a dictionary literal. Instead, create a new ordered dictionary using a + /// dictionary literal as its value by enclosing a comma-separated list of + /// key-value pairs in square brackets. You can use a dictionary literal + /// anywhere an ordered dictionary is expected by the type context. + /// + /// - Parameter elements: A variadic list of key-value pairs for the new + /// ordered dictionary. + /// + /// - Complexity: O(`elements.count`) if `Key` implements + /// high-quality hashing. + @inlinable + public init(dictionaryLiteral elements: (Key, Value)...) { + self.init(uniqueKeysWithValues: elements) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Hashable.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Hashable.swift new file mode 100644 index 0000000000..40375df8aa --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Hashable.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary: Hashable where Value: Hashable { + /// Hashes the essential components of this value by feeding them into the + /// given hasher. + /// + /// Complexity: O(`count`) + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(count) // Discriminator + for (key, value) in self { + hasher.combine(key) + hasher.combine(value) + } + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Initializers.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Initializers.swift new file mode 100644 index 0000000000..93352ef710 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Initializers.swift @@ -0,0 +1,455 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary { + /// Creates an empty dictionary. + /// + /// This initializer is equivalent to initializing with an empty dictionary + /// literal. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public init() { + self._keys = OrderedSet() + self._values = [] + } + + /// Creates an empty dictionary with preallocated space for at least the + /// specified number of elements. + /// + /// Use this initializer to avoid intermediate reallocations of a dictionary's + /// storage buffer when you know in advance how many elements you'll insert + /// into it after creation. + /// + /// If you have a good idea of the expected working size of the dictionary, + /// calling this initializer with `persistent` set to true can sometimes + /// improve performance by eliminating churn due to repeated rehashings when + /// the dictionary temporarily shrinks below its regular size. You can cancel + /// any capacity you've previously reserved by persistently reserving a + /// capacity of zero. (This also shrinks the hash table to the ideal size for + /// its current number elements.) + /// + /// - Parameter minimumCapacity: The minimum number of elements that the newly + /// created dictionary should be able to store without reallocating its + /// storage. + /// + /// - Parameter persistent: If set to true, prevent removals from shrinking + /// storage below the specified capacity. By default, removals are allowed + /// to shrink storage below any previously reserved capacity. + /// + /// - Complexity: O(`minimumCapacity`) + @inlinable + @inline(__always) + public init(minimumCapacity: Int, persistent: Bool = false) { + self._keys = OrderedSet(minimumCapacity: minimumCapacity, persistent: persistent) + self._values = [] + _values.reserveCapacity(minimumCapacity) + } +} + +extension OrderedDictionary { + /// Creates a new dictionary from the key-value pairs in the given sequence. + /// + /// You use this initializer to create a dictionary when you have a sequence + /// of key-value tuples with unique keys. Passing a sequence with duplicate + /// keys to this initializer results in a runtime error. If your + /// sequence might have duplicate keys, use the + /// `Dictionary(_:uniquingKeysWith:)` initializer instead. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use for + /// the new dictionary. Every key in `keysAndValues` must be unique. + /// + /// - Returns: A new dictionary initialized with the elements of + /// `keysAndValues`. + /// + /// - Precondition: The sequence must not have duplicate keys. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count if + /// key-value pairs, if `Key` implements high-quality hashing. + @_disfavoredOverload // https://github.com/apple/swift-collections/issues/125 + @inlinable + public init( + uniqueKeysWithValues keysAndValues: S + ) where S.Element == (key: Key, value: Value) { + if S.self == Dictionary.self { + self.init(_uncheckedUniqueKeysWithValues: keysAndValues) + return + } + self.init() + reserveCapacity(keysAndValues.underestimatedCount) + for (key, value) in keysAndValues { + guard _keys._append(key).inserted else { + preconditionFailure("Duplicate key: '\(key)'") + } + _values.append(value) + } + } + + /// Creates a new dictionary from the key-value pairs in the given sequence. + /// + /// You use this initializer to create a dictionary when you have a sequence + /// of key-value tuples with unique keys. Passing a sequence with duplicate + /// keys to this initializer results in a runtime error. If your + /// sequence might have duplicate keys, use the + /// `Dictionary(_:uniquingKeysWith:)` initializer instead. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use for + /// the new dictionary. Every key in `keysAndValues` must be unique. + /// + /// - Returns: A new dictionary initialized with the elements of + /// `keysAndValues`. + /// + /// - Precondition: The sequence must not have duplicate keys. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count if + /// key-value pairs, if `Key` implements high-quality hashing. + @inlinable + public init( + uniqueKeysWithValues keysAndValues: S + ) where S.Element == (Key, Value) { + self.init() + reserveCapacity(keysAndValues.underestimatedCount) + for (key, value) in keysAndValues { + guard _keys._append(key).inserted else { + preconditionFailure("Duplicate key: '\(key)'") + } + _values.append(value) + } + } +} + +extension OrderedDictionary { + /// Creates a new dictionary from separate sequences of keys and values. + /// + /// You use this initializer to create a dictionary when you have two + /// sequences with unique keys and their associated values, respectively. + /// Passing a `keys` sequence with duplicate keys to this initializer results + /// in a runtime error. + /// + /// - Parameter keys: A sequence of unique keys. + /// + /// - Parameter values: A sequence of values associated with items in `keys`. + /// + /// - Returns: A new dictionary initialized with the data in + /// `keys` and `values`. + /// + /// - Precondition: The sequence must not have duplicate keys, and `keys` and + /// `values` must contain an equal number of elements. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count if + /// key-value pairs, if `Key` implements high-quality hashing. + @inlinable + public init( + uniqueKeys keys: Keys, + values: Values + ) where Keys.Element == Key, Values.Element == Value { + let keys = ContiguousArray(keys) + let values = ContiguousArray(values) + precondition(keys.count == values.count, + "Mismatching element counts between keys and values") + self._keys = .init(keys) + self._values = values + precondition(_keys.count == _values.count, "Duplicate keys") + _checkInvariants() + } +} + +extension OrderedDictionary { + /// Creates a new dictionary from the key-value pairs in the given sequence, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// You use this initializer to create a dictionary when you have a sequence + /// of key-value tuples that might have duplicate keys. As the dictionary is + /// built, the initializer calls the `combine` closure with the current and + /// new values for any duplicate keys. Pass a closure as `combine` that + /// returns the value to use in the resulting dictionary: The closure can + /// choose between the two values, combine them to produce a new value, or + /// even throw an error. + /// + /// let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] + /// + /// let firstValues = OrderedDictionary( + /// pairsWithDuplicateKeys, + /// uniquingKeysWith: { (first, _) in first }) + /// // ["a": 1, "b": 2] + /// + /// let lastValues = OrderedDictionary( + /// pairsWithDuplicateKeys, + /// uniquingKeysWith: { (_, last) in last }) + /// // ["a": 3, "b": 4] + /// + /// - Parameters: + /// - keysAndValues: A sequence of key-value pairs to use for the new + /// dictionary. + /// - combine: A closure that is called with the values for any duplicate + /// keys that are encountered. The closure returns the desired value for + /// the final dictionary. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count of + /// key-value pairs, if `Key` implements high-quality hashing. + @_disfavoredOverload // https://github.com/apple/swift-collections/issues/125 + @inlinable + @inline(__always) + public init( + _ keysAndValues: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (key: Key, value: Value) { + self.init() + try self.merge(keysAndValues, uniquingKeysWith: combine) + } + + /// Creates a new dictionary from the key-value pairs in the given sequence, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// You use this initializer to create a dictionary when you have a sequence + /// of key-value tuples that might have duplicate keys. As the dictionary is + /// built, the initializer calls the `combine` closure with the current and + /// new values for any duplicate keys. Pass a closure as `combine` that + /// returns the value to use in the resulting dictionary: The closure can + /// choose between the two values, combine them to produce a new value, or + /// even throw an error. + /// + /// let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] + /// + /// let firstValues = OrderedDictionary( + /// pairsWithDuplicateKeys, + /// uniquingKeysWith: { (first, _) in first }) + /// // ["a": 1, "b": 2] + /// + /// let lastValues = OrderedDictionary( + /// pairsWithDuplicateKeys, + /// uniquingKeysWith: { (_, last) in last }) + /// // ["a": 3, "b": 4] + /// + /// - Parameters: + /// - keysAndValues: A sequence of key-value pairs to use for the new + /// dictionary. + /// - combine: A closure that is called with the values for any duplicate + /// keys that are encountered. The closure returns the desired value for + /// the final dictionary. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count of + /// key-value pairs, if `Key` implements high-quality hashing. + @inlinable + @inline(__always) + public init( + _ keysAndValues: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (Key, Value) { + self.init() + try self.merge(keysAndValues, uniquingKeysWith: combine) + } +} + +extension OrderedDictionary { + /// Creates a new dictionary whose keys are the groupings returned by the + /// given closure and whose values are arrays of the elements that returned + /// each key. + /// + /// The arrays in the "values" position of the new dictionary each contain at + /// least one element, with the elements in the same order as the source + /// sequence. + /// + /// The following example declares an array of names, and then creates a + /// dictionary from that array by grouping the names by first letter: + /// + /// let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"] + /// let studentsByLetter = OrderedDictionary(grouping: students, by: { $0.first! }) + /// // ["K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"], "E": ["Efua"]] + /// + /// The new `studentsByLetter` dictionary has three entries, with students' + /// names grouped by the keys `"E"`, `"K"`, and `"A"`. + /// + /// - Parameters: + /// - values: A sequence of values to group into a dictionary. + /// - keyForValue: A closure that returns a key for each element in + /// `values`. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count of + /// values, if `Key` implements high-quality hashing. + @inlinable + public init( + grouping values: S, + by keyForValue: (S.Element) throws -> Key + ) rethrows where Value: RangeReplaceableCollection, Value.Element == S.Element { + try self.init(_grouping: values, by: keyForValue) + } + + /// Creates a new dictionary whose keys are the groupings returned by the + /// given closure and whose values are arrays of the elements that returned + /// each key. + /// + /// The arrays in the "values" position of the new dictionary each contain at + /// least one element, with the elements in the same order as the source + /// sequence. + /// + /// The following example declares an array of names, and then creates a + /// dictionary from that array by grouping the names by first letter: + /// + /// let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"] + /// let studentsByLetter = OrderedDictionary(grouping: students, by: { $0.first! }) + /// // ["K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"], "E": ["Efua"]] + /// + /// The new `studentsByLetter` dictionary has three entries, with students' + /// names grouped by the keys `"E"`, `"K"`, and `"A"`. + /// + /// - Parameters: + /// - values: A sequence of values to group into a dictionary. + /// - keyForValue: A closure that returns a key for each element in + /// `values`. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count of + /// values, if `Key` implements high-quality hashing. + @inlinable + public init( + grouping values: S, + by keyForValue: (S.Element) throws -> Key + ) rethrows where Value == [S.Element] { + // Note: this extra overload is necessary to make type inference work + // for the `Value` type -- we want it to default to `[S.Element`]. + // (https://github.com/apple/swift-collections/issues/139) + try self.init(_grouping: values, by: keyForValue) + } + + @inlinable + internal init( + _grouping values: S, + by keyForValue: (S.Element) throws -> Key + ) rethrows where Value: RangeReplaceableCollection, Value.Element == S.Element { + self.init() + for value in values { + let key = try keyForValue(value) + self.updateValue(forKey: key, default: Value()) { array in + array.append(value) + } + } + } +} + +extension OrderedDictionary { + @inlinable + internal init( + _uncheckedUniqueKeysWithValues keysAndValues: S + ) where S.Element == (key: Key, value: Value) { + self.init() + reserveCapacity(keysAndValues.underestimatedCount) + for (key, value) in keysAndValues { + _keys._appendNew(key) + _values.append(value) + } + _checkInvariants() + } + + /// Creates a new dictionary from the key-value pairs in the given sequence, + /// which must not contain duplicate keys. + /// + /// In optimized builds, this initializer does not verify that the keys are + /// actually unique. This makes creating the dictionary somewhat faster if you + /// know for sure that the elements are unique (e.g., because they come from + /// another collection with guaranteed-unique members, such as a + /// `Dictionary`). However, if you accidentally call this initializer with + /// duplicate members, it can return a corrupt dictionary value that may be + /// difficult to debug. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use for + /// the new dictionary. Every key in `keysAndValues` must be unique. + /// + /// - Returns: A new dictionary initialized with the elements of + /// `keysAndValues`. + /// + /// - Precondition: The sequence must not have duplicate keys. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count if + /// key-value pairs, if `Key` implements high-quality hashing. + @_disfavoredOverload // https://github.com/apple/swift-collections/issues/125 + @inlinable + public init( + uncheckedUniqueKeysWithValues keysAndValues: S + ) where S.Element == (key: Key, value: Value) { +#if DEBUG + self.init(uniqueKeysWithValues: keysAndValues) +#else + self.init(_uncheckedUniqueKeysWithValues: keysAndValues) +#endif + } + + /// Creates a new dictionary from the key-value pairs in the given sequence, + /// which must not contain duplicate keys. + /// + /// In optimized builds, this initializer does not verify that the keys are + /// actually unique. This makes creating the dictionary somewhat faster if you + /// know for sure that the elements are unique (e.g., because they come from + /// another collection with guaranteed-unique members, such as a + /// `Dictionary`). However, if you accidentally call this initializer with + /// duplicate members, it can return a corrupt dictionary value that may be + /// difficult to debug. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use for + /// the new dictionary. Every key in `keysAndValues` must be unique. + /// + /// - Returns: A new dictionary initialized with the elements of + /// `keysAndValues`. + /// + /// - Precondition: The sequence must not have duplicate keys. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count if + /// key-value pairs, if `Key` implements high-quality hashing. + @inlinable + public init( + uncheckedUniqueKeysWithValues keysAndValues: S + ) where S.Element == (Key, Value) { + // Add tuple labels + let keysAndValues = keysAndValues.lazy.map { (key: $0.0, value: $0.1) } + self.init(uncheckedUniqueKeysWithValues: keysAndValues) + } +} + +extension OrderedDictionary { + /// Creates a new dictionary from separate sequences of unique keys and + /// associated values. + /// + /// In optimized builds, this initializer does not verify that the keys are + /// actually unique. This makes creating the dictionary somewhat faster if you + /// know for sure that the elements are unique (e.g., because they come from + /// another collection with guaranteed-unique members, such as a + /// `Dictionary`). However, if you accidentally call this initializer with + /// duplicate members, it can return a corrupt dictionary value that may be + /// difficult to debug. + /// + /// - Parameter keys: A sequence of unique keys. + /// + /// - Parameter values: A sequence of values associated with items in `keys`. + /// + /// - Returns: A new dictionary initialized with the data in + /// `keys` and `values`. + /// + /// - Precondition: The sequence must not have duplicate keys, and `keys` and + /// `values` must contain an equal number of elements. + /// + /// - Complexity: Expected O(*n*) on average, where *n* is the count if + /// key-value pairs, if `Key` implements high-quality hashing. + @inlinable + @inline(__always) + public init( + uncheckedUniqueKeys keys: Keys, + values: Values + ) where Keys.Element == Key, Values.Element == Value { +#if DEBUG + self.init(uniqueKeys: keys, values: values) +#else + self._keys = .init(uncheckedUniqueElements: keys) + self._values = .init(values) + precondition(_keys.count == _values.count) + _checkInvariants() +#endif + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Invariants.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Invariants.swift new file mode 100644 index 0000000000..fa3d01fe0c --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Invariants.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary { + #if COLLECTIONS_INTERNAL_CHECKS + @inline(never) @_effects(releasenone) + public func _checkInvariants() { + precondition(_keys.count == _values.count) + self._keys._checkInvariants() + } + #else + @inline(__always) @inlinable + public func _checkInvariants() {} + #endif // COLLECTIONS_INTERNAL_CHECKS +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Partial MutableCollection.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Partial MutableCollection.swift new file mode 100644 index 0000000000..35970a7f3d --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Partial MutableCollection.swift @@ -0,0 +1,193 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary { + /// Exchanges the key-value pairs at the specified indices of the dictionary. + /// + /// Both parameters must be valid indices below `endIndex`. Passing the same + /// index as both `i` and `j` has no effect. + /// + /// - Parameters: + /// - i: The index of the first value to swap. + /// - j: The index of the second value to swap. + /// + /// - Complexity: O(1) when the dictionary's storage isn't shared with another + /// value; O(`count`) otherwise. + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + _keys.swapAt(i, j) + _values.swapAt(i, j) + } + + /// Reorders the elements of the dictionary such that all the elements that + /// match the given predicate are after all the elements that don't match. + /// + /// After partitioning a collection, there is a pivot index `p` where + /// no element before `p` satisfies the `belongsInSecondPartition` + /// predicate and every element at or after `p` satisfies + /// `belongsInSecondPartition`. + /// + /// - Parameter belongsInSecondPartition: A predicate used to partition + /// the collection. All elements satisfying this predicate are ordered + /// after all elements not satisfying it. + /// - Returns: The index of the first element in the reordered collection + /// that matches `belongsInSecondPartition`. If no elements in the + /// collection match `belongsInSecondPartition`, the returned index is + /// equal to the collection's `endIndex`. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func partition( + by belongsInSecondPartition: (Element) throws -> Bool + ) rethrows -> Int { + let pivot = try _values.withUnsafeMutableBufferPointer { values in + try _keys._partition(values: values, by: belongsInSecondPartition) + } + _checkInvariants() + return pivot + } +} + +extension OrderedDictionary { + /// Sorts the collection in place, using the given predicate as the + /// comparison between elements. + /// + /// When you want to sort a collection of elements that don't conform to + /// the `Comparable` protocol, pass a closure to this method that returns + /// `true` when the first element should be ordered before the second. + /// + /// Alternatively, use this method to sort a collection of elements that do + /// conform to `Comparable` when you want the sort to be descending instead + /// of ascending. Pass the greater-than operator (`>`) operator as the + /// predicate. + /// + /// `areInIncreasingOrder` must be a *strict weak ordering* over the + /// elements. That is, for any elements `a`, `b`, and `c`, the following + /// conditions must hold: + /// + /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) + /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are + /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. + /// (Transitive comparability) + /// - Two elements are *incomparable* if neither is ordered before the other + /// according to the predicate. If `a` and `b` are incomparable, and `b` + /// and `c` are incomparable, then `a` and `c` are also incomparable. + /// (Transitive incomparability) + /// + /// The sorting algorithm is guaranteed to be stable. A stable sort + /// preserves the relative order of elements for which + /// `areInIncreasingOrder` does not establish an order. + /// + /// - Parameter areInIncreasingOrder: A predicate that returns `true` if its + /// first argument should be ordered before its second argument; + /// otherwise, `false`. If `areInIncreasingOrder` throws an error during + /// the sort, the elements may be in a different order, but none will be + /// lost. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + public mutating func sort( + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows { + // FIXME: Implement in-place sorting. + let temp = try self.sorted(by: areInIncreasingOrder) + precondition(temp.count == self.count) + temp.withUnsafeBufferPointer { source in + _keys = OrderedSet(uncheckedUniqueElements: source.lazy.map { $0.key }) + _values = ContiguousArray(source.lazy.map { $0.value }) + } + _checkInvariants() + } +} + +extension OrderedDictionary where Key: Comparable { + /// Sorts the dictionary in place. + /// + /// You can sort an ordered dictionary of keys that conform to the + /// `Comparable` protocol by calling this method. The key-value pairs are + /// sorted in ascending order. (`Value` doesn't need to conform to + /// `Comparable` because the keys are guaranteed to be unique.) + /// + /// The sorting algorithm is guaranteed to be stable. A stable sort + /// preserves the relative order of elements that compare as equal. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + public mutating func sort() { + sort { $0.key < $1.key } + } +} + +extension OrderedDictionary { + /// Shuffles the collection in place. + /// + /// Use the `shuffle()` method to randomly reorder the elements of an ordered + /// dictionary. + /// + /// This method is equivalent to calling ``shuffle(using:)``, passing in the + /// system's default random generator. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @inlinable + public mutating func shuffle() { + var generator = SystemRandomNumberGenerator() + shuffle(using: &generator) + } + + /// Shuffles the collection in place, using the given generator as a source + /// for randomness. + /// + /// You use this method to randomize the elements of a collection when you + /// are using a custom random number generator. For example, you can use the + /// `shuffle(using:)` method to randomly reorder the elements of an array. + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the collection. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + /// + /// - Note: The algorithm used to shuffle a collection may change in a future + /// version of Swift. If you're passing a generator that results in the + /// same shuffled order each time you run your program, that sequence may + /// change when your program is compiled using a different version of + /// Swift. + @inlinable + public mutating func shuffle( + using generator: inout T + ) { + guard count > 1 else { return } + var keys = self._keys.elements + var values = self._values + self = [:] + var amount = keys.count + var current = 0 + while amount > 1 { + let random = Int.random(in: 0 ..< amount, using: &generator) + amount -= 1 + keys.swapAt(current, current + random) + values.swapAt(current, current + random) + current += 1 + } + self = OrderedDictionary(uncheckedUniqueKeys: keys, values: values) + } +} + +extension OrderedDictionary { + /// Reverses the elements of the ordered dictionary in place. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func reverse() { + _keys.reverse() + _values.reverse() + } +} + diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Partial RangeReplaceableCollection.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Partial RangeReplaceableCollection.swift new file mode 100644 index 0000000000..bc5791bab5 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Partial RangeReplaceableCollection.swift @@ -0,0 +1,184 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +// The parts of RangeReplaceableCollection that OrderedDictionary is able to implement. + +extension OrderedDictionary { + /// Reserves enough space to store the specified number of elements. + /// + /// This method ensures that the dictionary has unique, mutable, contiguous + /// storage, with space allocated for at least the requested number of + /// elements. + /// + /// If you are adding a known number of elements to a dictionary, call this + /// method once before the first insertion to avoid multiple reallocations. + /// + /// Do not call this method in a loop -- it does not use an exponential + /// allocation strategy, so doing that can result in quadratic instead of + /// linear performance. + /// + /// - Parameter minimumCapacity: The minimum number of elements that the + /// dictionary should be able to store without reallocating its storage. + /// + /// - Complexity: O(`max(count, minimumCapacity)`) + @inlinable + public mutating func reserveCapacity(_ minimumCapacity: Int) { + self._keys.reserveCapacity(minimumCapacity) + self._values.reserveCapacity(minimumCapacity) + } + + /// Removes all members from the dictionary. + /// + /// - Parameter keepingCapacity: If `true`, the dictionary's storage capacity + /// is preserved; if `false`, the underlying storage is released. The + /// default is `false`. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { + _keys.removeAll(keepingCapacity: keepCapacity) + _values.removeAll(keepingCapacity: keepCapacity) + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// resulting gap. + /// + /// - Parameter index: The position of the element to remove. `index` must be + /// a valid index of the collection that is not equal to the collection's + /// end index. + /// + /// - Returns: The removed element. + /// + /// - Complexity: O(`count`) + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + let key = _keys.remove(at: index) + let value = _values.remove(at: index) + return (key, value) + } + + /// Removes the specified subrange of elements from the collection. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _keys.removeSubrange(bounds) + _values.removeSubrange(bounds) + } + + /// Removes the specified subrange of elements from the collection. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange( + _ bounds: R + ) where R.Bound == Int { + removeSubrange(bounds.relative(to: elements)) + } + + + /// Removes the last element of a non-empty dictionary. + /// + /// - Complexity: Expected to be O(`1`) on average, if `Element` implements + /// high-quality hashing. + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + precondition(!isEmpty, "Cannot remove last element of an empty collection") + return remove(at: count - 1) + } + + /// Removes the last `n` element of the dictionary. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the collection. + /// + /// - Complexity: Expected to be O(`n`) on average, if `Element` implements + /// high-quality hashing. + @inlinable + public mutating func removeLast(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in the collection") + _keys.removeLast(n) + _values.removeLast(n) + } + + /// Removes the first element of a non-empty dictionary. + /// + /// The members following the removed key-value pair need to be moved to close + /// the resulting gaps in the storage arrays. + /// + /// - Complexity: O(`count`). + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + precondition(!isEmpty, "Cannot remove first element of an empty collection") + return remove(at: 0) + } + + /// Removes the first `n` elements of the dictionary. + /// + /// The members following the removed items need to be moved to close the + /// resulting gaps in the storage arrays. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the set. + /// + /// - Complexity: O(`count`). + @inlinable + public mutating func removeFirst(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in the collection") + _keys.removeFirst(n) + _values.removeFirst(n) + } + + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a collection that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// dictionary as its argument and returns a Boolean value indicating + /// whether the element should be removed from the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll( + where shouldBeRemoved: (Self.Element) throws -> Bool + ) rethrows { + let pivot = try _values.withUnsafeMutableBufferPointer { values in + try _keys._halfStablePartition( + values: values, + by: shouldBeRemoved) + } + removeSubrange(pivot...) + _checkInvariants() + } +} + diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Sequence.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Sequence.swift new file mode 100644 index 0000000000..2e5c559387 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Sequence.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary: Sequence { + /// The element type of a dictionary: a tuple containing an individual + /// key-value pair. + public typealias Element = (key: Key, value: Value) + + /// The type that allows iteration over an ordered dictionary's elements. + @frozen + public struct Iterator: IteratorProtocol { + @usableFromInline + internal let _base: OrderedDictionary + + @usableFromInline + internal var _position: Int + + @inlinable + @inline(__always) + internal init(_base: OrderedDictionary) { + self._base = _base + self._position = 0 + } + + /// Advances to the next element and returns it, or nil if no next + /// element exists. + /// + /// - Complexity: O(1) + @inlinable + public mutating func next() -> Element? { + guard _position < _base._values.count else { return nil } + let result = (_base._keys[_position], _base._values[_position]) + _position += 1 + return result + } + } + + /// The number of elements in the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var underestimatedCount: Int { + count + } + + /// Returns an iterator over the elements of this collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func makeIterator() -> Iterator { + Iterator(_base: self) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Values.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Values.swift new file mode 100644 index 0000000000..7181fe6144 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary+Values.swift @@ -0,0 +1,381 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedDictionary { + /// A view of an ordered dictionary's values as a standalone collection. + @frozen + public struct Values { + @usableFromInline + internal var _base: OrderedDictionary + + @inlinable + @inline(__always) + internal init(_base: OrderedDictionary) { + self._base = _base + } + } +} + +extension OrderedDictionary.Values { + /// A read-only view of the contents of this collection as an array value. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var elements: Array { + Array(_base._values) + } +} + +extension OrderedDictionary.Values { + /// Calls a closure with a pointer to the collection's contiguous storage. + /// + /// Often, the optimizer can eliminate bounds checks within a collection + /// algorithm, but when that fails, invoking the same algorithm on the + /// buffer pointer passed into your closure lets you trade safety for speed. + /// + /// The pointer passed as an argument to `body` is valid only during the + /// execution of `withUnsafeBufferPointer(_:)`. Do not store or return the + /// pointer for later use. + /// + /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter that + /// points to the contiguous storage for the collection. If `body` has a + /// return value, that value is also used as the return value for the + /// `withUnsafeBufferPointer(_:)` method. The pointer argument is valid only + /// for the duration of the method's execution. + /// + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// - Complexity: O(1) (not counting the closure call) + @inlinable + @inline(__always) + public func withUnsafeBufferPointer( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R { + try _base._values.withUnsafeBufferPointer(body) + } + + /// Calls the given closure with a pointer to the collection's mutable + /// contiguous storage. + /// + /// Often, the optimizer can eliminate bounds checks within a collection + /// algorithm, but when that fails, invoking the same algorithm on the buffer + /// pointer passed into your closure lets you trade safety for speed. + /// + /// The pointer passed as an argument to `body` is valid only during the + /// execution of `withUnsafeMutableBufferPointer(_:)`. Do not store or return + /// the pointer for later use. + /// + /// - Parameter body: A closure with an `UnsafeMutableBufferPointer` parameter + /// that points to the contiguous storage for the collection. If `body` has + /// a return value, that value is also used as the return value for the + /// `withUnsafeMutableBufferPointer(_:)` method. The pointer argument is + /// valid only for the duration of the method's execution. + /// + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// - Complexity: O(1) (not counting the closure call) + @inlinable + @inline(__always) + public mutating func withUnsafeMutableBufferPointer( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R { + try _base._values.withUnsafeMutableBufferPointer(body) + } +} + +extension OrderedDictionary.Values: Sequence { + /// The element type of the collection. + public typealias Element = Value + + /// The type that allows iteration over the collection's elements. + public typealias Iterator = IndexingIterator +} + +extension OrderedDictionary.Values: RandomAccessCollection { + /// The index type for a dictionary's values view, `Int`. + /// + /// Indices in `Values` are integer offsets from the start of the collection. + public typealias Index = Int + + /// The type that represents the indices that are valid for subscripting the + /// `Values` collection, in ascending order. + public typealias Indices = Range + + /// The position of the first element in a nonempty dictionary. + /// + /// For an instance of `OrderedDictionary.Values`, `startIndex` is always + /// zero. If the dictionary is empty, `startIndex` is equal to `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { 0 } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// + /// In `OrderedDictionary.Values`, `endIndex` always equals the count of + /// elements. If the dictionary is empty, `endIndex` is equal to `startIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { _base._values.count } + + /// Returns the position immediately after the given index. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { i + 1 } + + /// Returns the position immediately before the given index. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { i - 1 } + + /// Replaces the given index with its successor. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { i += 1 } + + /// Replaces the given index with its predecessor. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { i -= 1 } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, or the returned value will not be a valid index. + /// + /// - Parameters: + /// - i: A valid index of the collection. + /// - distance: The distance to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls to + /// `index(after:)`. If `distance` is negative, this is the same value as + /// the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, unless the index passed as `limit` prevents offsetting + /// beyond those bounds. (Otherwise the returned value won't be a valid index + /// in the collection.) + /// + /// - Parameters: + /// - i: A valid index of the collection. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the collection to use as a limit. If + /// `distance > 0`, `limit` has no effect if it is less than `i`. + /// Likewise, if `distance < 0`, `limit` has no effect if it is greater + /// than `i`. + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + _base._values.index(i, offsetBy: distance, limitedBy: limit) + } + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. If `end` is equal to + /// `start`, the result is zero. + /// + /// - Returns: The distance between `start` and `end`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + end - start + } + + /// Call `body(p)`, where `p` is a buffer pointer to the collection’s + /// contiguous storage. `OrderedDictionary.Values` values always have + /// contiguous storage. + /// + /// - Parameter body: A function to call. The function must not escape its + /// unsafe buffer pointer argument. + /// + /// - Returns: The value returned by `body`. + /// + /// - Complexity: O(1) (ignoring time spent in `body`) + @inlinable + @inline(__always) + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + try _base._values.withUnsafeBufferPointer(body) + } +} + +extension OrderedDictionary.Values: MutableCollection { + /// Accesses the element at the specified position. This can be used to + /// perform in-place mutations on dictionary values. + /// + /// - Parameter index: The position of the element to access. `index` must be + /// greater than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public subscript(position: Int) -> Value { + get { + _base._values[position] + } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + yield &_base._values[position] + } + } + + /// Exchanges the values at the specified indices of the collection. (Leaving + /// their associated keys in the underlying dictionary at their original + /// position.) + /// + /// Both parameters must be valid indices below `endIndex`. Passing the same + /// index as both `i` and `j` has no effect. + /// + /// - Parameters: + /// - i: The index of the first value to swap. + /// - j: The index of the second value to swap. + /// + /// - Complexity: O(1) when the dictionary's storage isn't shared with another + /// value; O(`count`) otherwise. + @inlinable + @inline(__always) + public mutating func swapAt(_ i: Int, _ j: Int) { + _base._values.swapAt(i, j) + } + + /// Reorders the elements of the collection such that all the elements that + /// match the given predicate are after all the elements that don't match. + /// + /// This operation does not reorder the keys of the underlying dictionary, + /// just their associated values. + /// + /// After partitioning a collection, there is a pivot index `p` where + /// no element before `p` satisfies the `belongsInSecondPartition` + /// predicate and every element at or after `p` satisfies + /// `belongsInSecondPartition`. + /// + /// - Parameter belongsInSecondPartition: A predicate used to partition + /// the collection. All elements satisfying this predicate are ordered + /// after all elements not satisfying it. + /// - Returns: The index of the first element in the reordered collection + /// that matches `belongsInSecondPartition`. If no elements in the + /// collection match `belongsInSecondPartition`, the returned index is + /// equal to the collection's `endIndex`. + /// + /// - Complexity: O(`count`) + @inlinable + @inline(__always) + public mutating func partition( + by belongsInSecondPartition: (Value) throws -> Bool + ) rethrows -> Int { + try _base._values.partition(by: belongsInSecondPartition) + } + + /// Call `body(b)`, where `b` is an unsafe buffer pointer to the collection's + /// mutable contiguous storage. `OrderedDictionary.Values` always stores its + /// elements in contiguous storage. + /// + /// The supplied buffer pointer is only valid for the duration of the call. + /// + /// Often, the optimizer can eliminate bounds- and uniqueness-checks within an + /// algorithm, but when that fails, invoking the same algorithm on the unsafe + /// buffer supplied to `body` lets you trade safety for speed. + /// + /// - Parameters: + /// - body: The function to invoke. + /// + /// - Returns: The value returned by `body`, or `nil` if `body` wasn't called. + /// + /// - Complexity: O(1) when this instance has a unique reference to its + /// underlying storage; O(`count`) otherwise. (Not counting the call to + /// `body`.) + @inlinable + @inline(__always) + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + try _base._values.withUnsafeMutableBufferPointer(body) + } +} + +extension OrderedDictionary.Values: Equatable where Value: Equatable { + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + left.elementsEqual(right) + } +} + +extension OrderedDictionary.Values: Hashable where Value: Hashable { + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(count) // Discriminator + for item in self { + hasher.combine(item) + } + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary.swift b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary.swift new file mode 100644 index 0000000000..34e0e3ba54 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedDictionary/OrderedDictionary.swift @@ -0,0 +1,1044 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +/// An ordered collection of key-value pairs. +/// +/// Like the standard `Dictionary`, ordered dictionaries use a hash table to +/// ensure that no two entries have the same keys, and to efficiently look up +/// values corresponding to specific keys. However, like an `Array` (and +/// unlike `Dictionary`), ordered dictionaries maintain their elements in a +/// particular user-specified order, and they support efficient random-access +/// traversal of their entries. +/// +/// `OrderedDictionary` is a useful alternative to `Dictionary` when the order +/// of elements is important, or when you need to be able to efficiently access +/// elements at various positions within the collection. +/// +/// You can create an ordered dictionary with any key type that conforms to the +/// `Hashable` protocol. +/// +/// let responses: OrderedDictionary = [ +/// 200: "OK", +/// 403: "Access forbidden", +/// 404: "File not found", +/// 500: "Internal server error", +/// ] +/// +/// ### Equality of Ordered Dictionaries +/// +/// Two ordered dictionaries are considered equal if they contain the same +/// elements, and *in the same order*. This matches the concept of equality of +/// an `Array`, and it is different from the unordered `Dictionary`. +/// +/// let a: OrderedDictionary = [1: "one", 2: "two"] +/// let b: OrderedDictionary = [2: "two", 1: "one"] +/// a == b // false +/// b.swapAt(0, 1) // `b` now has value [1: "one", 2: "two"] +/// a == b // true +/// +/// (`OrderedDictionary` only conforms to `Equatable` when its `Value` is +/// equatable.) +/// +/// ### Dictionary Operations +/// +/// `OrderedDictionary` provides many of the same operations as `Dictionary`. +/// +/// For example, you can look up and add/remove values using the familiar +/// key-based subscript, returning an optional value: +/// +/// var dictionary: OrderedDictionary = [:] +/// dictionary["one"] = 1 +/// dictionary["two"] = 2 +/// dictionary["three"] // nil +/// // dictionary is now ["one": 1, "two": 2] +/// +/// If a new entry is added using the subscript setter, it gets appended to the +/// end of the dictionary. (So that by default, the dictionary contains its +/// elements in the order they were originally inserted.) +/// +/// `OrderedDictionary` also implements the variant of this subscript that takes +/// a default value. Like with `Dictionary`, this is useful when you want to +/// perform in-place mutations on values: +/// +/// let text = "short string" +/// var counts: OrderedDictionary = [:] +/// for character in text { +/// counts[character, default: 0] += 1 +/// } +/// // counts is ["s": 2, "h": 1, "o": 1, +/// // "r": 2, "t": 2, " ": 1, +/// // "i": 1, "n": 1, "g": 1] +/// +/// If the `Value` type implements reference semantics, or when you need to +/// perform a series of individual mutations on the values, the closure-based +/// `updateValue(forKey:default:_:)` method provides an easier-to-use +/// alternative to the defaulted key-based subscript. +/// +/// let text = "short string" +/// var counts: OrderedDictionary = [:] +/// for character in text { +/// counts.updateValue(forKey: character, default: 0) { value in +/// value += 1 +/// } +/// } +/// // Same result as before +/// +/// (This isn't currently available on the regular `Dictionary`.) +/// +/// The `Dictionary` type's original ``updateValue(_:forKey:)`` method is also +/// available, and so is ``index(forKey:)``, grouping/uniquing initializers +/// (``init(uniqueKeysWithValues:)-5ux9r``, ``init(_:uniquingKeysWith:)-2y39b``, +/// ``init(grouping:by:)-6mahw``), methods for merging one dictionary with +/// another (``merge(_:uniquingKeysWith:)-6ka2i``, +/// ``merging(_:uniquingKeysWith:)-4z49c``), filtering dictionary entries +/// (``filter(_:)``), transforming values (``mapValues(_:)``), +/// and a combination of these two (``compactMapValues(_:)``). +/// +/// ### Sequence and Collection Operations +/// +/// Ordered dictionaries use integer indices representing offsets from the +/// beginning of the collection. However, to avoid ambiguity between key-based +/// and indexing subscripts, `OrderedDictionary` doesn't directly conform to +/// `Collection`. Instead, it only conforms to `Sequence`, and provides a +/// random-access collection view over its key-value pairs, called +/// ``elements-swift.property``: +/// +/// responses[0] // `nil` (key-based subscript) +/// responses.elements[0] // `(200, "OK")` (index-based subscript) +/// +/// Because ordered dictionaries need to maintain unique keys, neither +/// `OrderedDictionary` nor its ``elements-swift.property`` view can conform to +/// the full `MutableCollection` or `RangeReplaceableCollection` protocols. +/// However, they are able to partially implement requirements: they support +/// mutations that merely change the order of elements, or just remove a subset +/// of existing members. +/// +/// `OrderedDictionary` also implements ``reserveCapacity(_:)`` from +/// `RangeReplaceableCollection`, to allow for efficient insertion of a known +/// number of elements. (However, unlike `Array` and `Dictionary`, +/// `OrderedDictionary` does not provide a `capacity` property.) +/// +/// ### Keys and Values Views +/// +/// Like the standard `Dictionary`, `OrderedDictionary` provides ``keys`` and +/// ``values-swift.property`` properties that provide lightweight views into +/// the corresponding parts of the dictionary. +/// +/// The ``keys`` collection is of type `OrderedSet`, containing all the +/// keys in the original dictionary. +/// +/// let d: OrderedDictionary = [2: "two", 1: "one", 0: "zero"] +/// d.keys // [2, 1, 0] as OrderedSet +/// +/// The ``keys`` property is read-only, so you cannot mutate the dictionary +/// through it. However, it returns an ordinary ordered set value, which can be +/// copied out and then mutated if desired. (Such mutations won't affect the +/// original dictionary value.) +/// +/// The ``values-swift.property`` property returns a mutable random-access +/// collection of the values in the dictionary: +/// +/// d.values // "two", "one", "zero" +/// d.values[2] = "nada" +/// // `d` is now [2: "two", 1: "one", 0: "nada"] +/// d.values.sort() +/// // `d` is now [2: "nada", 1: "one", 0: "two"] +/// +/// Both views store their contents in regular `Array` values, accessible +/// through their own `elements` property. +/// +/// ## Performance +/// +/// Like the standard `Dictionary` type, the performance of hashing operations +/// in `OrderedDictionary` is highly sensitive to the quality of hashing +/// implemented by the `Key` type. Failing to correctly implement hashing can +/// easily lead to unacceptable performance, with the severity of the effect +/// increasing with the size of the hash table. +/// +/// In particular, if a certain set of keys all produce the same hash value, +/// then hash table lookups regress to searching an element in an unsorted +/// array, i.e., a linear operation. To ensure hashed collection types exhibit +/// their target performance, it is important to ensure that such collisions +/// cannot be induced merely by adding a particular list of keys to the +/// dictionary. +/// +/// The easiest way to achieve this is to make sure `Key` implements hashing +/// following `Hashable`'s documented best practices. The conformance must +/// implement the `hash(into:)` requirement, and every bit of information that +/// is compared in `==` needs to be combined into the supplied `Hasher` value. +/// When used correctly, `Hasher` produces high-quality, randomly seeded hash +/// values that prevent repeatable hash collisions. +/// +/// When `Key` correctly conforms to `Hashable`, key-based lookups in an ordered +/// dictionary is expected to take O(1) equality checks on average. Hash +/// collisions can still occur organically, so the worst-case lookup performance +/// is technically still O(*n*) (where *n* is the size of the dictionary); +/// however, long lookup chains are unlikely to occur in practice. +/// +/// ## Implementation Details +/// +/// An ordered dictionary consists of an ordered set of keys, alongside a +/// regular `Array` value that contains their associated values. +@frozen +public struct OrderedDictionary { + @usableFromInline + internal var _keys: OrderedSet + + @usableFromInline + internal var _values: ContiguousArray + + @inlinable + @inline(__always) + internal init( + _uniqueKeys keys: OrderedSet, + values: ContiguousArray + ) { + self._keys = keys + self._values = values + } +} + +extension OrderedDictionary { + /// A read-only collection view for the keys contained in this dictionary, as + /// an `OrderedSet`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var keys: OrderedSet { _keys } + + /// A mutable collection view containing the values in this dictionary. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var values: Values { + get { Values(_base: self) } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + var values = Values(_base: self) + self = [:] + defer { self = values._base } + yield &values + } + } +} + +extension OrderedDictionary { + public typealias Index = Int + + /// A Boolean value indicating whether the dictionary is empty. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var isEmpty: Bool { _values.isEmpty } + + /// The number of elements in the dictionary. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _values.count } + + /// Returns the index for the given key. + /// + /// If the given key is found in the dictionary, this method returns an index + /// into the dictionary that corresponds with the key-value pair. + /// + /// let countryCodes: OrderedDictionary = ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"] + /// let index = countryCodes.index(forKey: "JP") + /// + /// print("Country code for \(countryCodes[offset: index!].value): '\(countryCodes[offset: index!].key)'.") + /// // Prints "Country code for Japan: 'JP'." + /// + /// - Parameter key: The key to find in the dictionary. + /// + /// - Returns: The index for `key` and its associated value if `key` is in + /// the dictionary; otherwise, `nil`. + /// + /// - Complexity: Expected to be O(1) on average, if `Key` implements + /// high-quality hashing. + @inlinable + @inline(__always) + public func index(forKey key: Key) -> Int? { + _keys.firstIndex(of: key) + } +} + +extension OrderedDictionary { + /// Accesses the value associated with the given key for reading and writing. + /// + /// This *key-based* subscript returns the value for the given key if the key + /// is found in the dictionary, or `nil` if the key is not found. + /// + /// The following example creates a new dictionary and prints the value of a + /// key found in the dictionary (`"Coral"`) and a key not found in the + /// dictionary (`"Cerise"`). + /// + /// var hues: OrderedDictionary = ["Heliotrope": 296, "Coral": 16, "Aquamarine": 156] + /// print(hues["Coral"]) + /// // Prints "Optional(16)" + /// print(hues["Cerise"]) + /// // Prints "nil" + /// + /// When you assign a value for a key and that key already exists, the + /// dictionary overwrites the existing value. If the dictionary doesn't + /// contain the key, the key and value are added as a new key-value pair. + /// + /// Here, the value for the key `"Coral"` is updated from `16` to `18` and a + /// new key-value pair is added for the key `"Cerise"`. + /// + /// hues["Coral"] = 18 + /// print(hues["Coral"]) + /// // Prints "Optional(18)" + /// + /// hues["Cerise"] = 330 + /// print(hues["Cerise"]) + /// // Prints "Optional(330)" + /// + /// If you assign `nil` as the value for the given key, the dictionary + /// removes that key and its associated value. + /// + /// In the following example, the key-value pair for the key `"Aquamarine"` + /// is removed from the dictionary by assigning `nil` to the key-based + /// subscript. + /// + /// hues["Aquamarine"] = nil + /// print(hues) + /// // Prints "["Coral": 18, "Heliotrope": 296, "Cerise": 330]" + /// + /// - Parameter key: The key to find in the dictionary. + /// + /// - Returns: The value associated with `key` if `key` is in the dictionary; + /// otherwise, `nil`. + /// + /// - Complexity: Looking up values in the dictionary through this subscript + /// has an expected complexity of O(1) hashing/comparison operations on + /// average, if `Key` implements high-quality hashing. Updating the + /// dictionary also has an amortized expected complexity of O(1) -- + /// although individual updates may need to copy or resize the dictionary's + /// underlying storage. + @inlinable + public subscript(key: Key) -> Value? { + get { + guard let index = _keys.firstIndex(of: key) else { return nil } + return _values[index] + } + set { + // We have a separate `set` in addition to `_modify` in hopes of getting + // rid of `_modify`'s swapAt dance in the usual case where the caller just + // wants to assign a new value. + let (index, bucket) = _keys._find(key) + switch (index, newValue) { + case let (index?, newValue?): // Assign + _values[index] = newValue + case let (index?, nil): // Remove + _keys._removeExistingMember(at: index, in: bucket) + _values.remove(at: index) + case let (nil, newValue?): // Insert + _keys._appendNew(key, in: bucket) + _values.append(newValue) + case (nil, nil): // Noop + break + } + _checkInvariants() + } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + var value: Value? + let (index, bucket) = _prepareForKeyingModify(key, &value) + defer { + _finalizeKeyingModify(key, index, bucket, &value) + } + yield &value + } + } + + @inlinable + internal mutating func _prepareForKeyingModify( + _ key: Key, + _ value: inout Value? + ) -> (index: Int?, bucket: _HashTable.Bucket) { + let (index, bucket) = _keys._find(key) + + // To support in-place mutations better, we swap the value to the end of + // the array, pop it off, then put things back in place when we're done. + if let index = index { + _values.swapAt(index, _values.count - 1) + value = _values.removeLast() + } + return (index, bucket) + } + + @inlinable + internal mutating func _finalizeKeyingModify( + _ key: Key, + _ index: Int?, + _ bucket: _HashTable.Bucket, + _ value: inout Value? + ) { + switch (index, value) { + case let (index?, value?): // Assign + _values.append(value) + _values.swapAt(index, _values.count - 1) + case let (index?, nil): // Remove + if index < _values.count { + let standin = _values.remove(at: index) + _values.append(standin) + } + _keys._removeExistingMember(at: index, in: bucket) + case let (nil, value?): // Insert + _keys._appendNew(key, in: bucket) + _values.append(value) + case (nil, nil): // Noop + break + } + _checkInvariants() + } + + /// Accesses the value with the given key. If the dictionary doesn't contain + /// the given key, accesses the provided default value as if the key and + /// default value existed in the dictionary. + /// + /// Use this subscript when you want either the value for a particular key + /// or, when that key is not present in the dictionary, a default value. This + /// example uses the subscript with a message to use in case an HTTP response + /// code isn't recognized: + /// + /// var responseMessages: OrderedDictionary = [ + /// 200: "OK", + /// 403: "Access forbidden", + /// 404: "File not found", + /// 500: "Internal server error"] + /// + /// let httpResponseCodes = [200, 403, 301] + /// for code in httpResponseCodes { + /// let message = responseMessages[code, default: "Unknown response"] + /// print("Response \(code): \(message)") + /// } + /// // Prints "Response 200: OK" + /// // Prints "Response 403: Access forbidden" + /// // Prints "Response 301: Unknown response" + /// + /// When a dictionary's `Value` type has value semantics, you can use this + /// subscript to perform in-place operations on values in the dictionary. + /// The following example uses this subscript while counting the occurrences + /// of each letter in a string: + /// + /// let message = "Hello, Elle!" + /// var letterCounts: [Character: Int] = [:] + /// for letter in message { + /// letterCounts[letter, default: 0] += 1 + /// } + /// // letterCounts == ["H": 1, "e": 2, "l": 4, "o": 1, ...] + /// + /// When `letterCounts[letter, defaultValue: 0] += 1` is executed with a + /// value of `letter` that isn't already a key in `letterCounts`, the + /// specified default value (`0`) is returned from the subscript, + /// incremented, and then added to the dictionary under that key. + /// + /// - Note: Do not use this subscript to modify dictionary values if the + /// dictionary's `Value` type is a class. In that case, the default value + /// and key are not written back to the dictionary after an operation. (For + /// a variant of this operation that supports this usecase, see + /// `updateValue(forKey:default:_:)`.) + /// + /// - Parameters: + /// - key: The key the look up in the dictionary. + /// - defaultValue: The default value to use if `key` doesn't exist in the + /// dictionary. + /// + /// - Returns: The value associated with `key` in the dictionary; otherwise, + /// `defaultValue`. + /// + /// - Complexity: Looking up values in the dictionary through this subscript + /// has an expected complexity of O(1) hashing/comparison operations on + /// average, if `Key` implements high-quality hashing. Updating the + /// dictionary also has an amortized expected complexity of O(1) -- + /// although individual updates may need to copy or resize the dictionary's + /// underlying storage. + @inlinable + public subscript( + key: Key, + default defaultValue: @autoclosure () -> Value + ) -> Value { + get { + guard let offset = _keys.firstIndex(of: key) else { return defaultValue() } + return _values[offset] + } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + var (index, value) = _prepareForDefaultedModify(key, defaultValue) + defer { + _finalizeDefaultedModify(index, &value) + } + yield &value + } + } + + @inlinable + internal mutating func _prepareForDefaultedModify( + _ key: Key, + _ defaultValue: () -> Value + ) -> (index: Int, value: Value) { + let (inserted, index) = _keys.append(key) + if inserted { + assert(index == _values.count) + _values.append(defaultValue()) + } + let value: Value = _values.withUnsafeMutableBufferPointer { buffer in + assert(index < buffer.count) + return (buffer.baseAddress! + index).move() + } + return (index, value) + } + + @inlinable + internal mutating func _finalizeDefaultedModify( + _ index: Int, _ value: inout Value + ) { + _values.withUnsafeMutableBufferPointer { buffer in + assert(index < buffer.count) + (buffer.baseAddress! + index).initialize(to: value) + } + } +} + +extension OrderedDictionary { + /// Updates the value stored in the dictionary for the given key, or appends a + /// new key-value pair if the key does not exist. + /// + /// Use this method instead of key-based subscripting when you need to know + /// whether the new value supplants the value of an existing key. If the + /// value of an existing key is updated, `updateValue(_:forKey:)` returns + /// the original value. + /// + /// var hues: OrderedDictionary = [ + /// "Heliotrope": 296, + /// "Coral": 16, + /// "Aquamarine": 156] + /// + /// if let oldValue = hues.updateValue(18, forKey: "Coral") { + /// print("The old value of \(oldValue) was replaced with a new one.") + /// } + /// // Prints "The old value of 16 was replaced with a new one." + /// + /// If the given key is not present in the dictionary, this method appends the + /// key-value pair and returns `nil`. + /// + /// if let oldValue = hues.updateValue(330, forKey: "Cerise") { + /// print("The old value of \(oldValue) was replaced with a new one.") + /// } else { + /// print("No value was found in the dictionary for that key.") + /// } + /// // Prints "No value was found in the dictionary for that key." + /// + /// - Parameters: + /// - value: The new value to add to the dictionary. + /// - key: The key to associate with `value`. If `key` already exists in + /// the dictionary, `value` replaces the existing associated value. If + /// `key` isn't already a key of the dictionary, the `(key, value)` pair + /// is added. + /// + /// - Returns: The value that was replaced, or `nil` if a new key-value pair + /// was added. + /// + /// - Complexity: expected complexity is amortized O(1), if `Key` implements + /// high-quality hashing. + @inlinable + @discardableResult + public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { + let (index, bucket) = _keys._find(key) + if let index = index { + let old = _values[index] + _values[index] = value + return old + } + _keys._appendNew(key, in: bucket) + _values.append(value) + return nil + } + + /// Updates the value stored in the dictionary for the given key, or inserts a + /// new key-value pair at the specified index if the key does not exist. + /// + /// Use this method instead of key-based subscripting when you need to insert + /// new keys at a particular index. You can use the return value to + /// determine whether or not the new value supplanted the value of an existing + /// key. + /// + /// If the value of an existing key is updated, + /// `updateValue(_:forKey:insertingAt:)` returns the original value and its + /// index. + /// + /// var hues: OrderedDictionary = [ + /// "Heliotrope": 296, + /// "Coral": 16, + /// "Aquamarine": 156] + /// let newIndex = hues.startIndex + /// let (old, index) = + /// hues.updateValue(18, forKey: "Coral", insertingAt: newIndex) + /// if let old = old { + /// print("The value '\(old)' at offset \(index.offset) was replaced.") + /// } + /// // Prints "The value '16' at offset 1 was replaced." + /// + /// If the given key is not present in the dictionary, this method inserts the + /// key-value pair at the specified index and returns `nil`. + /// + /// let (old, index) = + /// hues.updateValue(330, forKey: "Cerise", insertingAt: newIndex) + /// if let old = old { + /// print("The value '\(old)' at offset \(index.offset) was replaced.") + /// } else { + /// print("A new value was inserted at offset \(index.offset).") + /// } + /// // Prints "A new value was inserted at offset 0.") + /// + /// - Parameters: + /// - value: The new value to add to the dictionary. + /// - key: The key to associate with `value`. If `key` already exists in + /// the dictionary, `value` replaces the existing associated value. If + /// `key` isn't already a key of the dictionary, the `(key, value)` pair + /// is inserted. + /// - index: The index at which to insert the key, if it doesn't already + /// exist. + /// + /// - Returns: A pair `(old, index)`, where `old` is the value that was + /// replaced, or `nil` if a new key-value pair was added, and `index` + /// is the index corresponding to the updated (or inserted) value. + /// + /// - Complexity: O(`count`) + @inlinable + @discardableResult + public mutating func updateValue( + _ value: Value, + forKey key: Key, + insertingAt index: Int + ) -> (originalMember: Value?, index: Int) { + let (inserted, offset) = _keys.insert(key, at: index) + if inserted { + assert(offset == index) + _values.insert(value, at: offset) + return (nil, offset) + } + let old = _values[offset] + _values[offset] = value + return (old, offset) + } + + /// Ensures that the specified key exists in the dictionary (by appending one + /// with the supplied default value if necessary), then calls `body` to update + /// it in place. + /// + /// You can use this method to perform in-place operations on values in the + /// dictionary, whether or not `Value` has value semantics. The following + /// example uses this method while counting the occurrences of each letter + /// in a string: + /// + /// let message = "Hello, Elle!" + /// var letterCounts: [Character: Int] = [:] + /// for letter in message { + /// letterCounts.updateValue(forKey: letter, default: 0) { count in + /// count += 1 + /// } + /// } + /// // letterCounts == ["H": 1, "e": 2, "l": 4, "o": 1, ...] + /// + /// - Parameters: + /// - key: The key to look up (or append). If `key` does not already exist + /// in the dictionary, it is appended with the supplied default value. + /// - defaultValue: The default value to append if `key` doesn't exist in + /// the dictionary. + /// - body: A function that performs an in-place mutation on the dictionary + /// value. + /// + /// - Returns: The return value of `body`. + /// + /// - Complexity: expected complexity is amortized O(1), if `Key` implements + /// high-quality hashing. (Ignoring the complexity of calling `body`.) + @inlinable + public mutating func updateValue( + forKey key: Key, + default defaultValue: @autoclosure () -> Value, + with body: (inout Value) throws -> R + ) rethrows -> R { + let (index, bucket) = _keys._find(key) + if let index = index { + return try body(&_values[index]) + } + _keys._appendNew(key, in: bucket) + _values.append(defaultValue()) + let i = _values.index(before: _values.endIndex) + return try body(&_values[i]) + } + + /// Ensures that the specified key exists in the dictionary (by inserting one + /// with the specified index and default value if necessary), then calls + /// `body` to update it in place. + /// + /// You can use this method to perform in-place operations on values in the + /// dictionary, whether or not `Value` has value semantics. The following + /// example uses this method while counting the occurrences of each letter + /// in a string: + /// + /// let message = "Hello, Elle!" + /// var letterCounts: [Character: Int] = [:] + /// for letter in message { + /// letterCounts.updateValue(forKey: letter, default: 0) { count in + /// count += 1 + /// } + /// } + /// // letterCounts == ["H": 1, "e": 2, "l": 4, "o": 1, ...] + /// + /// - Parameters: + /// - key: The key to look up (or append). If `key` does not already exist + /// in the dictionary, it is appended with the supplied default value. + /// - defaultValue: The default value to append if `key` doesn't exist in + /// the dictionary. + /// - body: A function that performs an in-place mutation on the dictionary + /// value. + /// + /// - Returns: The return value of `body`. + /// + /// - Complexity: expected complexity is amortized O(1), if `Key` implements + /// high-quality hashing. (Ignoring the complexity of calling `body`.) + @inlinable + public mutating func updateValue( + forKey key: Key, + insertingDefault defaultValue: @autoclosure () -> Value, + at index: Int, + with body: (inout Value) throws -> R + ) rethrows -> R { + let (existingIndex, bucket) = _keys._find(key) + if let existingIndex = existingIndex { + return try body(&_values[existingIndex]) + } + _keys._insertNew(key, at: index, in: bucket) + _values.insert(defaultValue(), at: index) + return try body(&_values[index]) + } +} + +extension OrderedDictionary { + /// Removes the given key and its associated value from the dictionary. + /// + /// If the key is found in the dictionary, this method returns the key's + /// associated value. + /// + /// var hues: OrderedDictionary = [ + /// "Heliotrope": 296, + /// "Coral": 16, + /// "Aquamarine": 156] + /// if let value = hues.removeValue(forKey: "Coral") { + /// print("The value \(value) was removed.") + /// } + /// // Prints "The value 16 was removed." + /// + /// If the key isn't found in the dictionary, `removeValue(forKey:)` returns + /// `nil`. + /// + /// if let value = hues.removeValue(forKey: "Cerise") { + /// print("The value \(value) was removed.") + /// } else { + /// print("No value found for that key.") + /// } + /// // Prints "No value found for that key."" + /// + /// - Parameter key: The key to remove along with its associated value. + /// - Returns: The value that was removed, or `nil` if the key was not + /// present in the dictionary. + /// + /// - Complexity: O(`count`) + @inlinable + @discardableResult + public mutating func removeValue(forKey key: Key) -> Value? { + let (idx, bucket) = _keys._find(key) + guard let index = idx else { return nil } + _keys._removeExistingMember(at: index, in: bucket) + return _values.remove(at: index) + } +} + +extension OrderedDictionary { + /// Merges the key-value pairs in the given sequence into the dictionary, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// Use the `combine` closure to select a value to use in the updated + /// dictionary, or to combine existing and new values. As the key-value + /// pairs are merged with the dictionary, the `combine` closure is called + /// with the current and new values for any duplicate keys that are + /// encountered. + /// + /// This example shows how to choose the current or new values for any + /// duplicate keys: + /// + /// var dictionary: OrderedDictionary = ["a": 1, "b": 2] + /// + /// // Keeping existing value for key "a": + /// dictionary.merge(zip(["a", "c"], [3, 4])) { (current, _) in current } + /// // ["a": 1, "b": 2, "c": 4] + /// + /// // Taking the new value for key "a": + /// dictionary.merge(zip(["a", "d"], [5, 6])) { (_, new) in new } + /// // ["a": 5, "b": 2, "c": 4, "d": 6] + /// + /// This operation preserves the order of keys in the original dictionary. + /// New key-value pairs are appended to the end in the order they appear in + /// the given sequence. + /// + /// - Parameters: + /// - keysAndValues: A sequence of key-value pairs. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// dictionary. + /// + /// - Complexity: Expected to be O(*n*) on average, where *n* is the number of + /// elements in `keysAndValues`, if `Key` implements high-quality hashing. + @_disfavoredOverload // https://github.com/apple/swift-collections/issues/125 + @inlinable + public mutating func merge( + _ keysAndValues: __owned S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (key: Key, value: Value) { + for (key, value) in keysAndValues { + let (index, bucket) = _keys._find(key) + if let index = index { + try { $0 = try combine($0, value) }(&_values[index]) + } else { + _keys._appendNew(key, in: bucket) + _values.append(value) + } + } + } + + /// Merges the key-value pairs in the given sequence into the dictionary, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// Use the `combine` closure to select a value to use in the updated + /// dictionary, or to combine existing and new values. As the key-value + /// pairs are merged with the dictionary, the `combine` closure is called + /// with the current and new values for any duplicate keys that are + /// encountered. + /// + /// This example shows how to choose the current or new values for any + /// duplicate keys: + /// + /// var dictionary: OrderedDictionary = ["a": 1, "b": 2] + /// + /// // Keeping existing value for key "a": + /// dictionary.merge(zip(["a", "c"], [3, 4])) { (current, _) in current } + /// // ["a": 1, "b": 2, "c": 4] + /// + /// // Taking the new value for key "a": + /// dictionary.merge(zip(["a", "d"], [5, 6])) { (_, new) in new } + /// // ["a": 5, "b": 2, "c": 4, "d": 6] + /// + /// This operation preserves the order of keys in the original dictionary. + /// New key-value pairs are appended to the end in the order they appear in + /// the given sequence. + /// + /// - Parameters: + /// - keysAndValues: A sequence of key-value pairs. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// dictionary. + /// + /// - Complexity: Expected to be O(*n*) on average, where *n* is the number of + /// elements in `keysAndValues`, if `Key` implements high-quality hashing. + @inlinable + public mutating func merge( + _ keysAndValues: __owned S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (Key, Value) { + let mapped: LazyMapSequence = + keysAndValues.lazy.map { (key: $0.0, value: $0.1) } + try merge(mapped, uniquingKeysWith: combine) + } + + /// Creates a dictionary by merging key-value pairs in a sequence into this + /// dictionary, using a combining closure to determine the value for + /// duplicate keys. + /// + /// Use the `combine` closure to select a value to use in the returned + /// dictionary, or to combine existing and new values. As the key-value + /// pairs are merged with the dictionary, the `combine` closure is called + /// with the current and new values for any duplicate keys that are + /// encountered. + /// + /// This example shows how to choose the current or new values for any + /// duplicate keys: + /// + /// let dictionary: OrderedDictionary = ["a": 1, "b": 2] + /// let newKeyValues = zip(["a", "b"], [3, 4]) + /// + /// let keepingCurrent = dictionary.merging(newKeyValues) { (current, _) in current } + /// // ["a": 1, "b": 2] + /// let replacingCurrent = dictionary.merging(newKeyValues) { (_, new) in new } + /// // ["a": 3, "b": 4] + /// + /// - Parameters: + /// - other: A sequence of key-value pairs. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// dictionary. + /// + /// - Returns: A new dictionary with the combined keys and values of this + /// dictionary and `other`. The order of keys in the result dictionary + /// matches that of `self`, with additional key-value pairs (if any) + /// appended at the end in the order they appear in `other`. + /// + /// - Complexity: Expected to be O(`count` + *n*) on average, where *n* is the + /// number of elements in `keysAndValues`, if `Key` implements high-quality + /// hashing. + @_disfavoredOverload // https://github.com/apple/swift-collections/issues/125 + @inlinable + public __consuming func merging( + _ other: __owned S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows -> Self where S.Element == (key: Key, value: Value) { + var copy = self + try copy.merge(other, uniquingKeysWith: combine) + return copy + } + + /// Creates a dictionary by merging key-value pairs in a sequence into this + /// dictionary, using a combining closure to determine the value for + /// duplicate keys. + /// + /// Use the `combine` closure to select a value to use in the returned + /// dictionary, or to combine existing and new values. As the key-value + /// pairs are merged with the dictionary, the `combine` closure is called + /// with the current and new values for any duplicate keys that are + /// encountered. + /// + /// This example shows how to choose the current or new values for any + /// duplicate keys: + /// + /// let dictionary: OrderedDictionary = ["a": 1, "b": 2] + /// let newKeyValues = zip(["a", "b"], [3, 4]) + /// + /// let keepingCurrent = dictionary.merging(newKeyValues) { (current, _) in current } + /// // ["a": 1, "b": 2] + /// let replacingCurrent = dictionary.merging(newKeyValues) { (_, new) in new } + /// // ["a": 3, "b": 4] + /// + /// - Parameters: + /// - other: A sequence of key-value pairs. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// dictionary. + /// + /// - Returns: A new dictionary with the combined keys and values of this + /// dictionary and `other`. The order of keys in the result dictionary + /// matches that of `self`, with additional key-value pairs (if any) + /// appended at the end in the order they appear in `other`. + /// + /// - Complexity: Expected to be O(`count` + *n*) on average, where *n* is the + /// number of elements in `keysAndValues`, if `Key` implements high-quality + /// hashing. + @inlinable + public __consuming func merging( + _ other: __owned S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows -> Self where S.Element == (Key, Value) { + var copy = self + try copy.merge(other, uniquingKeysWith: combine) + return copy + } +} + +extension OrderedDictionary { + /// Returns a new dictionary containing the key-value pairs of the dictionary + /// that satisfy the given predicate. + /// + /// - Parameter isIncluded: A closure that takes a key-value pair as its + /// argument and returns a Boolean value indicating whether the pair + /// should be included in the returned dictionary. + /// + /// - Returns: A dictionary of the key-value pairs that `isIncluded` allows, + /// in the same order that they appear in `self`. + /// + /// - Complexity: O(`count`) + @inlinable + public func filter( + _ isIncluded: (Element) throws -> Bool + ) rethrows -> Self { + var result: OrderedDictionary = [:] + for element in self where try isIncluded(element) { + result._keys._appendNew(element.key) + result._values.append(element.value) + } + return result + } +} + +extension OrderedDictionary { + /// Returns a new dictionary containing the keys of this dictionary with the + /// values transformed by the given closure. + /// + /// - Parameter transform: A closure that transforms a value. `transform` + /// accepts each value of the dictionary as its parameter and returns a + /// transformed value of the same or of a different type. + /// - Returns: A dictionary containing the keys and transformed values of + /// this dictionary, in the same order. + /// + /// - Complexity: O(`count`) + @inlinable + public func mapValues( + _ transform: (Value) throws -> T + ) rethrows -> OrderedDictionary { + OrderedDictionary( + _uniqueKeys: _keys, + values: ContiguousArray(try _values.map(transform))) + } + + /// Returns a new dictionary containing only the key-value pairs that have + /// non-`nil` values as the result of transformation by the given closure. + /// + /// Use this method to receive a dictionary with non-optional values when + /// your transformation produces optional values. + /// + /// In this example, note the difference in the result of using `mapValues` + /// and `compactMapValues` with a transformation that returns an optional + /// `Int` value. + /// + /// let data: OrderedDictionary = ["a": "1", "b": "three", "c": "///4///"] + /// + /// let m: [String: Int?] = data.mapValues { str in Int(str) } + /// // ["a": Optional(1), "b": nil, "c": nil] + /// + /// let c: [String: Int] = data.compactMapValues { str in Int(str) } + /// // ["a": 1] + /// + /// - Parameter transform: A closure that transforms a value. `transform` + /// accepts each value of the dictionary as its parameter and returns an + /// optional transformed value of the same or of a different type. + /// + /// - Returns: A dictionary containing the keys and non-`nil` transformed + /// values of this dictionary, in the same order. + /// + /// - Complexity: O(`count`) + @inlinable + public func compactMapValues( + _ transform: (Value) throws -> T? + ) rethrows -> OrderedDictionary { + var result: OrderedDictionary = [:] + for (key, value) in self { + if let value = try transform(value) { + result._keys._appendNew(key) + result._values.append(value) + } + } + return result + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Codable.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Codable.swift new file mode 100644 index 0000000000..365d225f9c --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Codable.swift @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet: Encodable where Element: Encodable { + /// Encodes the elements of this ordered set into the given encoder. + /// + /// - Parameter encoder: The encoder to write data to. + @inlinable + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(_elements) + } +} + +extension OrderedSet: Decodable where Element: Decodable { + /// Creates a new ordered set by decoding from the given decoder. + /// + /// This initializer throws an error if reading from the decoder fails, or + /// if the decoded contents contain duplicate values. + /// + /// - Parameter decoder: The decoder to read data from. + @inlinable + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let elements = try container.decode(ContiguousArray.self) + + let (table, end) = _HashTable.create(untilFirstDuplicateIn: elements) + guard end == elements.endIndex else { + let context = DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Decoded elements aren't unique (first duplicate at offset \(end))") + throw DecodingError.dataCorrupted(context) + } + self.init( + _uniqueElements: elements, + elements.count > _HashTable.maximumUnhashedCount ? table : nil) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomDebugStringConvertible.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomDebugStringConvertible.swift new file mode 100644 index 0000000000..effbe7d3ad --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomDebugStringConvertible.swift @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet: CustomDebugStringConvertible { + /// A textual representation of this instance, suitable for debugging. + public var debugDescription: String { + _debugDescription(typeName: _debugTypeName()) + } + + internal func _debugTypeName() -> String { + "OrderedSet<\(Element.self)>" + } + + internal func _debugDescription(typeName: String) -> String { + var result = "\(typeName)([" + var first = true + for item in self { + if first { + first = false + } else { + result += ", " + } + debugPrint(item, terminator: "", to: &result) + } + result += "])" + return result + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomReflectable.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomReflectable.swift new file mode 100644 index 0000000000..8b05462852 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomReflectable.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet: CustomReflectable { + /// The custom mirror for this instance. + public var customMirror: Mirror { + Mirror(self, unlabeledChildren: _elements, displayStyle: .collection) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomStringConvertible.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomStringConvertible.swift new file mode 100644 index 0000000000..2678ed60a0 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+CustomStringConvertible.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet: CustomStringConvertible { + /// A textual representation of this instance. + public var description: String { + var result = "[" + var first = true + for item in self { + if first { + first = false + } else { + result += ", " + } + print(item, terminator: "", to: &result) + } + result += "]" + return result + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Diffing.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Diffing.swift new file mode 100644 index 0000000000..95b60b5e45 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Diffing.swift @@ -0,0 +1,100 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet { + /// Returns the collection difference between the parameter and the + /// receiver, using an algorithm specialized to exploit fast membership + /// testing and the member uniqueness guarantees of `OrderedSet`. + /// + /// - Complexity: O(`self.count + other.count`) + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + public func difference( + from other: Self + ) -> CollectionDifference { + /* While admitting that variables with names like "a", "b", "x", and "y" are not especially readable, their use (and meaning) is standard in the diffing literature and familiarity with that literature will help if you're reading this code anyway. */ + let a = other // The collection we're diffing "from". An element present in this collection but not the other is a "remove" + var x = 0 // The working index into `a` + let b = self // The collection we're diffing "to". An element present in this collection but not the other is an "insert" + var y = 0 // The working index into `b` + + var changes = Array.Change>() + func remove() { + let ax = a[x] + changes.append(.remove(offset: x, element: ax, associatedWith: b.lastIndex(of: ax))) + x += 1 + } + func insert() { + let by = b[y] + changes.append(.insert(offset: y, element: by, associatedWith: a.lastIndex(of: by))) + y += 1 + } + + while x < a.count || y < b.count { + if y == b.count { + // No more elements to process in `b`, `a[x]` must have been removed + remove() + } else if x == a.count { + // No more elements to process in `a`, `b[y]` must have been inserted + insert() + } else if let axinb = b.lastIndex(of: a[x]) { + if axinb < y { + // Element has already been processed as an insertion in `b`, generate associated remove for move + remove() + } + else if let byina = a.lastIndex(of: b[y]) { + if byina < x { + // Element has already been processed as a remove in `a`, generate associated insert for move + insert() + } else if x == byina { + assert(y == axinb) + // `a[x]` == `b[y]` + x += 1; y += 1 + } else if byina - x < axinb - y { + // `a[x]` exists further away from the current position in `b` than `b[y] does in `a` + insert() + } else { + // `b[y]` exists further away from the current position in `a` than `a[x]` does in `b` + remove() + } + } else { + // `b[y]` does not exist in `a`, the element must have been inserted + insert() + } + } else { + // `a[x]` does not exist in `b`, the element must have been removed + remove() + } + } + return CollectionDifference(changes)! + } + + /* Replicating RangeReplaceableCollection.applying here would involve + * duplicating a ton of IPI from the stdlib. Dropping down to Array and back + * to OrderedSet is still O(n)-ish which still beats the naïve application + * algorithm of "remove, then insert" + */ + /// Applies the given difference to this collection. + /// + /// - Parameter difference: The difference to be applied. + /// + /// - Returns: An instance representing the state of the receiver with the + /// difference applied, or `nil` if the difference is incompatible with + /// the receiver's state. + /// + /// - Complexity: O(*n* + *c*), where *n* is `self.count` and *c* + /// is the number of changes contained by the parameter. + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + public func applying(_ difference: CollectionDifference) -> Self? { + guard let array = self.elements.applying(difference) else { return nil } + let result = OrderedSet(array) + return result.count == array.count ? result : nil + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Equatable.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Equatable.swift new file mode 100644 index 0000000000..1190cd6e74 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Equatable.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet: Equatable { + /// Returns a Boolean value indicating whether two values are equal. + /// + /// Two ordered sets are considered equal if they contain the same + /// elements in the same order. + /// + /// - Complexity: O(`min(left.count, right.count)`) + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + left.elementsEqual(right) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+ExpressibleByArrayLiteral.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+ExpressibleByArrayLiteral.swift new file mode 100644 index 0000000000..1d1d6dece8 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+ExpressibleByArrayLiteral.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet: ExpressibleByArrayLiteral { + /// Creates a new ordered set from the contents of an array literal. + /// + /// Duplicate elements in the literal are allowed, but the resulting ordered + /// set will only contain the first occurrence of each. + /// + /// Do not call this initializer directly. It is used by the compiler when + /// you use an array literal. Instead, create a new ordered set using an array + /// literal as its value by enclosing a comma-separated list of values in + /// square brackets. You can use an array literal anywhere an ordered set is + /// expected by the type context. + /// + /// - Parameter elements: A variadic list of elements of the new ordered set. + /// + /// - Complexity: O(`elements.count`) if `Element` implements + /// high-quality hashing. + @inlinable + public init(arrayLiteral elements: Element...) { + self.init(elements) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Hashable.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Hashable.swift new file mode 100644 index 0000000000..3acba7206e --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Hashable.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet: Hashable { + /// Hashes the essential components of this value by feeding them into the + /// given hasher. + /// + /// Complexity: O(`count`) + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(count) // Discriminator + for item in _elements { + hasher.combine(item) + } + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Initializers.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Initializers.swift new file mode 100644 index 0000000000..e9b68df6e8 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Initializers.swift @@ -0,0 +1,152 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet { + /// Creates a set with the contents of the given sequence, which + /// must not include duplicate elements. + /// + /// In optimized builds, this initializer does not verify that the + /// elements are actually unique. This makes creating the set + /// somewhat faster if you know for sure that the elements are + /// unique (e.g., because they come from another collection with + /// guaranteed-unique members, such as a `Set`). However, if you + /// accidentally call this initializer with duplicate members, it + /// can return a corrupt set value that may be difficult to debug. + /// + /// - Parameter elements: A finite sequence of unique elements. + /// + /// - Complexity: Expected to be O(*n*) on average, where *n* is the + /// number of elements in the sequence, if `Element` implements + /// high-quality hashing. + @inlinable + @inline(__always) + public init(uncheckedUniqueElements elements: S) + where S.Element == Element { + let elements = ContiguousArray(elements) +#if DEBUG + let (table, firstDupe) = _HashTable.create(untilFirstDuplicateIn: elements) + precondition(firstDupe == elements.endIndex, + "Duplicate elements found in input") +#else + let table = _HashTable.create(uncheckedUniqueElements: elements) +#endif + self.init( + _uniqueElements: elements, + elements.count > _HashTable.maximumUnhashedCount ? table : nil) + _checkInvariants() + } +} + +extension OrderedSet { + /// Creates a new set from a finite sequence of items. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// The sequence is allowed to contain duplicate elements, but only + /// the first duplicate instance is preserved in the result. + /// + /// - Complexity: This operation is expected to perform O(*n*) + /// hashing and equality comparisons on average (where *n* + /// is the number of elements in the sequence), provided that + /// `Element` properly implements hashing. + @inlinable + public init(_ elements: S) where S.Element == Element { + if S.self == Self.self { + self = elements as! Self + return + } + // Fast paths for when we know elements are all unique + if S.self == Set.self || S.self == SubSequence.self { + self.init(uncheckedUniqueElements: elements) + return + } + + self.init() + append(contentsOf: elements) + } + + // Specializations + + /// Creates a new set from a an existing set. This is functionally the same as + /// copying the value of `elements` into a new variable. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: O(1) + @inlinable + public init(_ elements: Self) { + self = elements + } + + /// Creates a new set from an existing slice of another set. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: This operation is expected to perform + /// O(`elements.count`) operations on average, provided that + /// `Element` implements high-quality hashing. + @inlinable + public init(_ elements: SubSequence) { + self.init(uncheckedUniqueElements: elements._slice) + } + + /// Creates a new set from an existing `Set` value. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: This operation is expected to perform + /// O(`elements.count`) operations on average, provided that + /// `Element` implements high-quality hashing. + @inlinable + public init(_ elements: Set) { + self.init(uncheckedUniqueElements: elements) + } + + /// Creates a new set from the keys view of a dictionary. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: This operation is expected to perform + /// O(`elements.count`) operations on average, provided that + /// `Element` implements high-quality hashing. + @inlinable + public init(_ elements: Dictionary.Keys) { + self._elements = ContiguousArray(elements) + _regenerateHashTable() + _checkInvariants() + } + + /// Creates a new set from a collection of items. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: This operation is expected to perform O(*n*) + /// comparisons on average (where *n* is the number of elements + /// in the sequence), provided that `Element` implements + /// high-quality hashing. + @inlinable + public init( + _ elements: C + ) where C.Element == Element { + // This code is careful not to copy storage if `C` is an Array + // or ContiguousArray and the elements are already unique. + let (table, firstDupe) = _HashTable.create( + untilFirstDuplicateIn: elements) + if firstDupe == elements.endIndex { + // Fast path: `elements` consists of unique values. + self.init(_uniqueElements: ContiguousArray(elements), table) + return + } + + // Otherwise keep the elements we've processed and add the rest one by one. + self.init(_uniqueElements: ContiguousArray(elements[.. (inserted: Bool, index: Int) { + let (index, bucket) = _find(item) + if let index = index { return (false, index) } + _appendNew(item, in: bucket) + return (true, _elements.index(before: _elements.endIndex)) + } + + /// Append a new member to the end of the set, if the set doesn't + /// already contain it. + /// + /// - Parameter item: The element to add to the set. + /// + /// - Returns: A pair `(inserted, index)`, where `inserted` is a Boolean value + /// indicating whether the operation added a new element, and `index` is + /// the index of `item` in the resulting set. + /// + /// - Complexity: The operation is expected to perform O(1) copy, hash, and + /// compare operations on the `Element` type, if it implements high-quality + /// hashing. + @inlinable + @inline(__always) + @discardableResult + public mutating func append(_ item: Element) -> (inserted: Bool, index: Int) { + let result = _append(item) + _checkInvariants() + return result + } + + /// Append the contents of a sequence to the end of the set, excluding + /// elements that are already members. + /// + /// This is functionally equivalent to `self.formUnion(elements)`, but it's + /// more explicit about how the new members are ordered in the new set. + /// + /// - Parameter elements: A finite sequence of elements to append. + /// + /// - Complexity: The operation is expected to perform amortized O(1) copy, + /// hash, and compare operations on the `Element` type, if it implements + /// high-quality hashing. + @inlinable + public mutating func append( + contentsOf elements: S + ) where S.Element == Element { + for item in elements { + _append(item) + } + _checkInvariants() + } +} + +extension OrderedSet { + @inlinable + internal mutating func _insertNew( + _ item: Element, + at index: Int, + in bucket: _Bucket + ) { + guard _elements.count < _capacity else { + _elements.insert(item, at: index) + _regenerateHashTable() + return + } + guard _table != nil else { + _elements.insert(item, at: index) + return + } + + _ensureUnique() + _table!.update { hashTable in + assert(!hashTable.isOccupied(bucket)) + hashTable.adjustContents(preparingForInsertionOfElementAtOffset: index, in: _elements) + hashTable[bucket] = index + } + _elements.insert(item, at: index) + _checkInvariants() + } + + /// Insert a new member to this set at the specified index, if the set doesn't + /// already contain it. + /// + /// - Parameter item: The element to insert. + /// + /// - Returns: A pair `(inserted, index)`, where `inserted` is a Boolean value + /// indicating whether the operation added a new element, and `index` is + /// the index of `item` in the resulting set. If `inserted` is false, then + /// the returned `index` may be different from the index requested. + /// + /// - Complexity: The operation is expected to perform amortized + /// O(`self.count`) copy, hash, and compare operations on the `Element` + /// type, if it implements high-quality hashing. (Insertions need to make + /// room in the storage array to add the inserted element.) + @inlinable + @discardableResult + public mutating func insert( + _ item: Element, + at index: Int + ) -> (inserted: Bool, index: Int) { + let (existing, bucket) = _find(item) + if let existing = existing { return (false, existing) } + _insertNew(item, at: index, in: bucket) + return (true, index) + } +} + +extension OrderedSet { + /// Replace the member at the given index with a new value that compares equal + /// to it. + /// + /// This is useful when equal elements can be distinguished by identity + /// comparison or some other means. + /// + /// - Parameter item: The new value that should replace the original element. + /// `item` must compare equal to the original value. + /// + /// - Parameter index: The index of the element to be replaced. + /// + /// - Returns: The original element that was replaced. + /// + /// - Complexity: Amortized O(1). + @inlinable + @discardableResult + public mutating func update(_ item: Element, at index: Int) -> Element { + let old = _elements[index] + precondition( + item == old, + "The replacement item must compare equal to the original") + _elements[index] = item + return old + } +} + +extension OrderedSet { + /// Adds the given element to the set unconditionally, either appending it to + /// the set, or replacing an existing value if it's already present. + /// + /// This is useful when equal elements can be distinguished by identity + /// comparison or some other means. + /// + /// - Parameter item: The value to append or replace. + /// + /// - Returns: The original element that was replaced by this operation, or + /// `nil` if the value was appended to the end of the collection. + /// + /// - Complexity: The operation is expected to perform amortized O(1) copy, + /// hash, and compare operations on the `Element` type, if it implements + /// high-quality hashing. + @inlinable + @discardableResult + public mutating func updateOrAppend(_ item: Element) -> Element? { + let (inserted, index) = _append(item) + if inserted { return nil } + let old = _elements[index] + _elements[index] = item + _checkInvariants() + return old + } + + /// Adds the given element into the set unconditionally, either inserting it + /// at the specified index, or replacing an existing value if it's already + /// present. + /// + /// This is useful when equal elements can be distinguished by identity + /// comparison or some other means. + /// + /// - Parameter item: The value to append or replace. + /// + /// - Parameter index: The index at which to insert the new member if `item` + /// isn't already in the set. + /// + /// - Returns: The original element that was replaced by this operation, or + /// `nil` if the value was newly inserted into the collection. + /// + /// - Complexity: The operation is expected to perform amortized O(1) copy, + /// hash, and compare operations on the `Element` type, if it implements + /// high-quality hashing. + @inlinable + @discardableResult + public mutating func updateOrInsert( + _ item: Element, + at index: Int + ) -> (originalMember: Element?, index: Int) { + let (existing, bucket) = _find(item) + if let existing = existing { + let old = _elements[existing] + _elements[existing] = item + return (old, existing) + } + _insertNew(item, at: index, in: bucket) + return (nil, index) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Invariants.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Invariants.swift new file mode 100644 index 0000000000..51c853b7cf --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Invariants.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet { + #if COLLECTIONS_INTERNAL_CHECKS + @inlinable + @inline(never) @_effects(releasenone) + public func _checkInvariants() { + if _table == nil { + precondition(_elements.count <= _HashTable.maximumUnhashedCount, + "Oversized set without a hash table") + precondition(Set(_elements).count == _elements.count, + "Duplicate items in set") + return + } + // Check that each element in _elements can be found in the hash table. + for index in _elements.indices { + let item = _elements[index] + let i = _find(item).index + precondition(i != nil, + "Index \(index) not found in hash table (element: \(item))") + precondition( + i == index, + "Offset of element '\(item)' in hash table differs from its position") + } + // Check that the hash table has exactly as many entries as there are elements. + _table!.read { hashTable in + var it = hashTable.bucketIterator(startingAt: _Bucket(offset: 0)) + var c = 0 + repeat { + it.advance() + if it.isOccupied { c += 1 } + } while it.currentBucket.offset != 0 + precondition( + c == _elements.count, + """ + Number of entries in hash table (\(c)) differs + from number of elements (\(_elements.count)) + """) + } + } + #else + @inline(__always) @inlinable + public func _checkInvariants() {} + #endif // COLLECTIONS_INTERNAL_CHECKS +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial MutableCollection.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial MutableCollection.swift new file mode 100644 index 0000000000..953b8a7aa2 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial MutableCollection.swift @@ -0,0 +1,442 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +// The parts of MutableCollection that OrderedSet is able to implement. + +extension OrderedSet { + /// Exchanges the values at the specified indices of the set. + /// + /// Both parameters must be valid indices below `endIndex`. Passing the same + /// index as both `i` and `j` has no effect. + /// + /// - Parameters: + /// - i: The index of the first value to swap. + /// - j: The index of the second value to swap. + /// + /// - Complexity: O(1) when the set's storage isn't shared with another + /// value; O(`count`) otherwise. + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + guard i != j else { return } + _elements.swapAt(i, j) + guard _table != nil else { return } + _ensureUnique() + _table!.update { hashTable in + hashTable.swapBucketValues(for: _elements[i], withCurrentValue: j, + and: _elements[j], withCurrentValue: i) + } + _checkInvariants() + } + + /// Reorders the elements of the set such that all the elements that match the + /// given predicate are after all the elements that don't match. + /// + /// After partitioning a collection, there is a pivot index `p` where + /// no element before `p` satisfies the `belongsInSecondPartition` + /// predicate and every element at or after `p` satisfies + /// `belongsInSecondPartition`. + /// + /// In the following example, an ordered set of numbers is partitioned by a + /// predicate that matches elements greater than 30. + /// + /// var numbers: OrderedSet = [30, 40, 20, 30, 30, 60, 10] + /// let p = numbers.partition(by: { $0 > 30 }) + /// // p == 5 + /// // numbers == [30, 10, 20, 30, 30, 60, 40] + /// + /// The `numbers` set is now arranged in two partitions. The first partition, + /// `numbers[.. Bool + ) rethrows -> Int { + try _partition(by: belongsInSecondPartition, callback: { a, b in }) + } +} + +extension OrderedSet { + @inlinable + public mutating func _partition( + by belongsInSecondPartition: (Element) throws -> Bool, + callback: (Int, Int) -> Void + ) rethrows -> Int { + guard _table != nil else { + return try _elements.partition(by: belongsInSecondPartition) + } + _ensureUnique() + let result: Int = try _table!.update { hashTable in + let maybeOffset: Int? = try _elements.withContiguousMutableStorageIfAvailable { buffer in + let pivot = try buffer._partition( + with: hashTable, + by: belongsInSecondPartition, + callback: callback) + return pivot - buffer.startIndex + } + if let offset = maybeOffset { + return _elements.index(startIndex, offsetBy: offset) + } + return try _elements._partition( + with: hashTable, + by: belongsInSecondPartition, + callback: callback) + } + _checkInvariants() + return result + } +} + +extension MutableCollection where Self: RandomAccessCollection, Element: Hashable { + @inlinable + internal mutating func _partition( + with hashTable: _UnsafeHashTable, + by belongsInSecondPartition: (Element) throws -> Bool, + callback: (Int, Int) -> Void + ) rethrows -> Index { + var low = startIndex + var high = endIndex + + while true { + // Invariants at this point: + // - low <= high + // - all elements in `startIndex ..< low` belong in the first partition + // - all elements in `high ..< endIndex` belong in the second partition + + // Find next element from `lo` that may not be in the right place. + while true { + if low == high { return low } + if try belongsInSecondPartition(self[low]) { break } + formIndex(after: &low) + } + + // Find next element down from `hi` that we can swap `lo` with. + while true { + formIndex(before: &high) + if low == high { return low } + if try !belongsInSecondPartition(self[high]) { break } + } + + // Swap the two elements as well as their associated hash table buckets. + swapAt(low, high) + let offsetLow = _offset(of: low) + let offsetHigh = _offset(of: high) + hashTable.swapBucketValues(for: self[low], withCurrentValue: offsetHigh, + and: self[high], withCurrentValue: offsetLow) + callback(offsetLow, offsetHigh) + + formIndex(after: &low) + } + } +} + +extension _UnsafeHashTable { + @inlinable + @inline(__always) + func swapBucketValues( + for left: Element, withCurrentValue leftValue: Int, + and right: Element, withCurrentValue rightValue: Int + ) { + let left = idealBucket(for: left) + let right = idealBucket(for: right) + swapBucketValues(for: left, withCurrentValue: leftValue, + and: right, withCurrentValue: rightValue) + } + + @usableFromInline + @_effects(releasenone) + func swapBucketValues( + for left: Bucket, withCurrentValue leftValue: Int, + and right: Bucket, withCurrentValue rightValue: Int + ) { + var it = bucketIterator(startingAt: left) + it.advance(until: leftValue) + assert(it.isOccupied) + it.currentValue = rightValue + + it = bucketIterator(startingAt: right) + it.advance(until: rightValue) + assert(it.isOccupied) + // Note: this second update may mistake the bucket for `right` with the + // bucket for `left` whose value we just updated. The second update will + // restore the original hash table contents in this case. This is okay! + // When this happens, the lookup chains for both elements include each + // other, so leaving the hash table unchanged still leaves us with a + // working hash table. + it.currentValue = leftValue + } +} + +extension OrderedSet { + /// Sorts the collection in place, using the given predicate as the + /// comparison between elements. + /// + /// When you want to sort a collection of elements that don't conform to + /// the `Comparable` protocol, pass a closure to this method that returns + /// `true` when the first element should be ordered before the second. + /// + /// Alternatively, use this method to sort a collection of elements that do + /// conform to `Comparable` when you want the sort to be descending instead + /// of ascending. Pass the greater-than operator (`>`) operator as the + /// predicate. + /// + /// `areInIncreasingOrder` must be a *strict weak ordering* over the + /// elements. That is, for any elements `a`, `b`, and `c`, the following + /// conditions must hold: + /// + /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) + /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are + /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. + /// (Transitive comparability) + /// - Two elements are *incomparable* if neither is ordered before the other + /// according to the predicate. If `a` and `b` are incomparable, and `b` + /// and `c` are incomparable, then `a` and `c` are also incomparable. + /// (Transitive incomparability) + /// + /// The sorting algorithm is guaranteed to be stable. A stable sort + /// preserves the relative order of elements for which + /// `areInIncreasingOrder` does not establish an order. + /// + /// - Parameter areInIncreasingOrder: A predicate that returns `true` if its + /// first argument should be ordered before its second argument; + /// otherwise, `false`. If `areInIncreasingOrder` throws an error during + /// the sort, the elements may be in a different order, but none will be + /// lost. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + public mutating func sort( + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows { + defer { + // Note: This assumes that `sort(by:)` won't leave duplicate/missing + // elements in the table when the closure throws. This matches the + // stdlib's behavior in Swift 5.3, and it seems like a reasonable + // long-term assumption. + _regenerateExistingHashTable() + _checkInvariants() + } + try _elements.sort(by: areInIncreasingOrder) + } +} + +extension OrderedSet where Element: Comparable { + /// Sorts the set in place. + /// + /// You can sort an ordered set of elements that conform to the + /// `Comparable` protocol by calling this method. Elements are sorted in + /// ascending order. + /// + /// Here's an example of sorting a list of students' names. Strings in Swift + /// conform to the `Comparable` protocol, so the names are sorted in + /// ascending order according to the less-than operator (`<`). + /// + /// var students: OrderedSet = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"] + /// students.sort() + /// print(students) + /// // Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter"]" + /// + /// To sort the elements of your collection in descending order, pass the + /// greater-than operator (`>`) to the `sort(by:)` method. + /// + /// students.sort(by: >) + /// print(students) + /// // Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]" + /// + /// The sorting algorithm is guaranteed to be stable. A stable sort + /// preserves the relative order of elements that compare as equal. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + public mutating func sort() { + defer { + // Note: This assumes that `sort(by:)` won't leave duplicate/missing + // elements in the table when the closure throws. This matches the + // stdlib's behavior in Swift 5.3, and it seems like a reasonable + // long-term assumption. + _regenerateExistingHashTable() + _checkInvariants() + } + _elements.sort() + } +} + +extension OrderedSet { + /// Shuffles the collection in place. + /// + /// Use the `shuffle()` method to randomly reorder the elements of an ordered + /// set. + /// + /// var names: OrderedSet + /// = ["Alejandro", "Camila", "Diego", "Luciana", "Luis", "Sofía"] + /// names.shuffle() + /// // names == ["Luis", "Camila", "Luciana", "Sofía", "Alejandro", "Diego"] + /// + /// This method is equivalent to calling `shuffle(using:)`, passing in the + /// system's default random generator. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @inlinable + public mutating func shuffle() { + var generator = SystemRandomNumberGenerator() + shuffle(using: &generator) + } + + /// Shuffles the collection in place, using the given generator as a source + /// for randomness. + /// + /// You use this method to randomize the elements of a collection when you + /// are using a custom random number generator. For example, you can use the + /// `shuffle(using:)` method to randomly reorder the elements of an array. + /// + /// var names: OrderedSet + /// = ["Alejandro", "Camila", "Diego", "Luciana", "Luis", "Sofía"] + /// names.shuffle(using: &myGenerator) + /// // names == ["Sofía", "Alejandro", "Camila", "Luis", "Diego", "Luciana"] + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the collection. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + /// + /// - Note: The algorithm used to shuffle a collection may change in a future + /// version of Swift. If you're passing a generator that results in the + /// same shuffled order each time you run your program, that sequence may + /// change when your program is compiled using a different version of + /// Swift. + @inlinable + public mutating func shuffle( + using generator: inout T + ) { + _elements.shuffle(using: &generator) + _regenerateExistingHashTable() + _checkInvariants() + } +} + +extension OrderedSet { + /// Reverses the elements of the ordered set in place. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func reverse() { + _elements.reverse() + // FIXME: Update hash table contents in place. + _regenerateHashTable() + _checkInvariants() + } +} + +extension OrderedSet { + + /// Moves all elements satisfying `belongsInSecondPartition` into a suffix + /// of the collection, returning the start position of the resulting suffix. + /// On return, the items before this pivot index remain in the order they + /// originally appeared in the collection. + /// + /// - Complexity: O(*n*) where n is the length of the collection. + @inlinable + internal mutating func _halfStablePartition( + values: UnsafeMutableBufferPointer, + by belongsInSecondPartition: ((key: Element, value: Value)) throws -> Bool + ) rethrows -> Int { + precondition(self.count == values.count) + var i = 0 + try _elements.withUnsafeMutableBufferPointer { keys in + while i < keys.count, try !belongsInSecondPartition((keys[i], values[i])) { + i += 1 + } + } + guard i < self.count else { return self.count } + + self._ensureUnique() + let table = _table + self._table = nil + defer { self._table = table } + + return try _elements.withUnsafeMutableBufferPointer { keys in + for j in i + 1 ..< keys.count { + guard try !belongsInSecondPartition((keys[j], values[j])) else { + continue + } + keys.swapAt(i, j) + values.swapAt(i, j) + table?.update { hashTable in + hashTable.swapBucketValues(for: keys[i], withCurrentValue: j, + and: keys[j], withCurrentValue: i) + } + i += 1 + } + return i + } + } + + @inlinable + internal mutating func _partition( + values: UnsafeMutableBufferPointer, + by belongsInSecondPartition: ((key: Element, value: Value)) throws -> Bool + ) rethrows -> Int { + self._ensureUnique() + let table = self._table + self._table = nil + defer { self._table = table } + return try _elements.withUnsafeMutableBufferPointer { keys in + assert(keys.count == values.count) + var low = keys.startIndex + var high = keys.endIndex + + while true { + // Invariants at this point: + // - low <= high + // - all elements in `startIndex ..< low` belong in the first partition + // - all elements in `high ..< endIndex` belong in the second partition + + // Find next element from `lo` that may not be in the right place. + while true { + if low == high { return low } + if try belongsInSecondPartition((keys[low], values[low])) { break } + low += 1 + } + + // Find next element down from `hi` that we can swap `lo` with. + while true { + high -= 1 + if low == high { return low } + if try !belongsInSecondPartition((keys[high], values[high])) { break } + } + + // Swap the two elements as well as their associated hash table buckets. + keys.swapAt(low, high) + values.swapAt(low, high) + table?.update { hashTable in + hashTable.swapBucketValues(for: keys[low], withCurrentValue: high, + and: keys[high], withCurrentValue: low) + } + low += 1 + } + } + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial RangeReplaceableCollection.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial RangeReplaceableCollection.swift new file mode 100644 index 0000000000..fd8750d08a --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial RangeReplaceableCollection.swift @@ -0,0 +1,224 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +// The parts of RangeReplaceableCollection that OrderedSet is able to implement. + +extension OrderedSet { + /// Removes all members from the set. + /// + /// - Parameter keepingCapacity: If `true`, the set's storage capacity is + /// preserved; if `false`, the underlying storage is released. The default + /// is `false`. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { + _elements.removeAll(keepingCapacity: keepCapacity) + guard keepCapacity else { + _table = nil + return + } + guard _table != nil else { return } + _ensureUnique() + _table!.update { hashTable in + hashTable.clear() + } + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// resulting gap. + /// + /// - Parameter index: The position of the element to remove. `index` must be + /// a valid index of the collection that is not equal to the collection's + /// end index. + /// + /// - Returns: The removed element. + /// + /// - Complexity: O(`count`) + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Self.Element { + _elements._failEarlyRangeCheck(index, bounds: startIndex ..< endIndex) + let bucket = _bucket(for: index) + return _removeExistingMember(at: index, in: bucket) + } + + /// Removes the specified subrange of elements from the collection. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _elements._failEarlyRangeCheck( + bounds, + bounds: _elements.startIndex ..< _elements.endIndex) + guard _table != nil else { + _elements.removeSubrange(bounds) + _checkInvariants() + return + } + let c = bounds.count + guard c > 0 else { return } + let remainingCount = _elements.count - c + if remainingCount <= count / 2 || remainingCount < _minimumCapacity { + // Just generate a new table from scratch. + _elements.removeSubrange(bounds) + _regenerateHashTable() + _checkInvariants() + return + } + + _ensureUnique() + _table!.update { hashTable in + // Delete the hash table entries for all members we're removing. + for item in _elements[bounds] { + let (offset, bucket) = hashTable._find(item, in: _elements) + precondition(offset != nil, "Corrupt hash table") + hashTable.delete( + bucket: bucket, + hashValueGenerator: { offset, seed in + return _elements[offset]._rawHashValue(seed: seed) + }) + } + hashTable.adjustContents(preparingForRemovalOf: bounds, in: _elements) + } + _elements.removeSubrange(bounds) + _checkInvariants() + } + + /// Removes the specified subrange of elements from the collection. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange( + _ bounds: R + ) where R.Bound == Int { + removeSubrange(bounds.relative(to: self)) + } + + /// Removes the last element of a non-empty set. + /// + /// - Complexity: Expected to be O(`1`) on average, if `Element` implements + /// high-quality hashing. + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + precondition(!isEmpty, "Cannot remove last element of an empty collection") + guard _table != nil else { + return _elements.removeLast() + } + guard _elements.count - 1 >= _minimumCapacity else { + let old = _elements.removeLast() + _regenerateHashTable() + return old + } + defer { _checkInvariants() } + let old = _elements.removeLast() + _ensureUnique() + _table!.update { hashTable in + var it = hashTable.bucketIterator(for: old) + it.advance(until: _elements.count) + // Delete the entry for the removed member. + hashTable.delete( + bucket: it.currentBucket, + hashValueGenerator: { offset, seed in + _elements[offset]._rawHashValue(seed: seed) + }) + } + return old + } + + /// Removes the last `n` element of the set. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the collection. + /// + /// - Complexity: Expected to be O(`n`) on average, if `Element` implements + /// high-quality hashing. + @inlinable + public mutating func removeLast(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in the collection") + removeSubrange(count - n ..< count) + } + + /// Removes the first element of a non-empty set. + /// + /// The members following the removed item need to be moved to close the + /// resulting gap in the storage array. + /// + /// - Complexity: O(`count`). + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + precondition(!isEmpty, "Cannot remove first element of an empty collection") + return remove(at: startIndex) + } + + /// Removes the first `n` elements of the set. + /// + /// The members following the removed items need to be moved to close the + /// resulting gap in the storage array. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the set. + /// + /// - Complexity: O(`count`). + @inlinable + public mutating func removeFirst(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in the collection") + removeSubrange(0 ..< n) + } + + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a collection that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// This example removes all the odd values from an + /// array of numbers: + /// + /// var numbers: OrderedSet = [5, 6, 7, 8, 9, 10, 11] + /// numbers.removeAll(where: { !$0.isMultiple(of: 2) }) + /// // numbers == [6, 8, 10] + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// set as its argument and returns a Boolean value indicating + /// whether the element should be removed from the set. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll( + where shouldBeRemoved: (Element) throws -> Bool + ) rethrows { + defer { + _regenerateHashTable() + _checkInvariants() + } + try _elements.removeAll(where: shouldBeRemoved) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Basics.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Basics.swift new file mode 100644 index 0000000000..ecc6f7b5d2 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Basics.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +// `OrderedSet` does not directly conform to `SetAlgebra` because its definition +// of equality conflicts with `SetAlgebra` requirements. However, it still +// implements most `SetAlgebra` requirements (except `insert`, which is replaced +// by `append`). +// +// `OrderedSet` also provides an `unordered` view that explicitly conforms to +// `SetAlgebra`. That view implements `Equatable` by ignoring element order, +// so it can satisfy `SetAlgebra` requirements. + +extension OrderedSet { + /// Creates an empty set. + /// + /// This initializer is equivalent to initializing with an empty array + /// literal. + /// + /// - Complexity: O(1) + @inlinable + public init() { + __storage = nil + _elements = [] + } +} + +extension OrderedSet { + /// Returns a Boolean value that indicates whether the given element exists + /// in the set. + /// + /// - Parameter element: An element to look for in the set. + /// + /// - Returns: `true` if `member` exists in the set; otherwise, `false`. + /// + /// - Complexity: This operation is expected to perform O(1) comparisons on + /// average, provided that `Element` implements high-quality hashing. + @inlinable + public func contains(_ element: Element) -> Bool { + _find_inlined(element).index != nil + } +} + +extension OrderedSet { + /// Removes the given element from the set. + /// + /// - Parameter member: The element of the set to remove. + /// + /// - Returns: The element equal to `member` if `member` is contained in the + /// set; otherwise, `nil`. In some cases, the returned element may be + /// distinguishable from `member` by identity comparison or some other + /// means. + /// + /// - Complexity: O(`count`). Removing an element from the middle of the + /// underlying ordered set needs to rearrange the remaining elements to + /// close the resulting gap. + /// + /// Removing the last element only takes (amortized) O(1) + /// hashing/comparisons operations, if `Element` implements high quality + /// hashing. + @inlinable + @discardableResult + public mutating func remove(_ member: Element) -> Element? { + let (idx, bucket) = _find(member) + guard let index = idx else { return nil } + return _removeExistingMember(at: index, in: bucket) + } +} + diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Operations.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Operations.swift new file mode 100644 index 0000000000..474d743123 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Operations.swift @@ -0,0 +1,589 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +// `OrderedSet` does not directly conform to `SetAlgebra` because its definition +// of equality conflicts with `SetAlgebra` requirements. However, it still +// implements most `SetAlgebra` requirements (except `insert`, which is replaced +// by `append`). +// +// `OrderedSet` also provides an `unordered` view that explicitly conforms to +// `SetAlgebra`. That view implements `Equatable` by ignoring element order, +// so it can satisfy `SetAlgebra` requirements. + +extension OrderedSet { + /// Adds the elements of the given set to this set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the set, in the order they appear in `other`. + /// + /// var a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [0, 2, 4, 6] + /// a.formUnion(b) + /// // `a` is now `[1, 2, 3, 4, 0, 6]` + /// + /// - Parameter other: The set of elements to insert. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public mutating func formUnion(_ other: __owned Self) { + append(contentsOf: other) + } + + /// Returns a new set with the elements of both this and the given set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the result, in the order they appear in `other`. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [0, 2, 4, 6] + /// a.union(b) // [1, 2, 3, 4, 0, 6] + /// + /// - Parameter other: The set of elements to add. + /// + /// - Complexity: Expected to be O(`self.count` + `other.count`) on average, + /// if `Element` implements high-quality hashing. + @inlinable + public __consuming func union(_ other: __owned Self) -> Self { + var result = self + result.formUnion(other) + return result + } + + // Generalizations + + /// Adds the elements of the given set to this set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the set, in the order they appear in `other`. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [0, 2, 4, 6] + /// a.formUnion(b.unordered) + /// // a is now [1, 2, 3, 4, 0, 6] + /// + /// - Parameter other: The set of elements to add. + /// + /// - Complexity: Expected to be O(`self.count` + `other.count`) on average, + /// if `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public mutating func formUnion(_ other: __owned UnorderedView) { + formUnion(other._base) + } + + /// Returns a new set with the elements of both this and the given set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the result, in the order they appear in `other`. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [0, 2, 4, 6] + /// a.union(b.unordered) // [1, 2, 3, 4, 0, 6] + /// + /// - Parameter other: The set of elements to add. + /// + /// - Complexity: Expected to be O(`self.count` + `other.count`) on average, + /// if `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public __consuming func union(_ other: __owned UnorderedView) -> Self { + union(other._base) + } + + /// Adds the elements of the given sequence to this set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the set, in the order they appear in `other`. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Array = [0, 2, 4, 6] + /// a.formUnion(b) + /// // a is now [1, 2, 3, 4, 0, 6] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(`self.count` + `other.count`) on average, + /// if `Element` implements high-quality hashing. + @inlinable + public mutating func formUnion( + _ other: __owned S + ) where S.Element == Element { + append(contentsOf: other) + } + + /// Returns a new set with the elements of both this and the given set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the result, in the order they appear in `other`. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Array = [0, 2, 4, 6] + /// a.union(b) // [1, 2, 3, 4, 0, 6] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(`self.count` + `other.count`) on average, + /// if `Element` implements high-quality hashing. + @inlinable + public __consuming func union( + _ other: __owned S + ) -> Self where S.Element == Element { + var result = self + result.formUnion(other) + return result + } +} + +extension OrderedSet { + /// Returns a new set with the elements that are common to both this set and + /// the provided other one, in the order they appear in `self`. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.intersection(other) // [2, 4] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public __consuming func intersection(_ other: Self) -> Self { + var result = Self() + for item in self { + if other.contains(item) { + result._appendNew(item) + } + } + result._checkInvariants() + return result + } + + /// Removes the elements of this set that aren't also in the given one. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.formIntersection(other) + /// // set is now [2, 4] + /// + /// - Parameter other: A set of elements. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public mutating func formIntersection(_ other: Self) { + self = self.intersection(other) + } + + // Generalizations + + /// Returns a new set with the elements that are common to both this set and + /// the provided other one, in the order they appear in `self`. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.intersection(other) // [2, 4] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + @inline(__always) + public __consuming func intersection(_ other: UnorderedView) -> Self { + intersection(other._base) + } + + /// Removes the elements of this set that aren't also in the given one. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.formIntersection(other) + /// // set is now [2, 4] + /// + /// - Parameter other: A set of elements. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + @inline(__always) + public mutating func formIntersection(_ other: UnorderedView) { + formIntersection(other._base) + } + + /// Returns a new set with the elements that are common to both this set and + /// the provided sequence, in the order they appear in `self`. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// set.intersection([6, 4, 2, 0] as Array) // [2, 4] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(*n*) on average where *n* is the number of + /// elements in `other`, if `Element` implements high-quality hashing. + @inlinable + public __consuming func intersection( + _ other: S + ) -> Self where S.Element == Element { + _UnsafeBitset.withTemporaryBitset(capacity: self.count) { bitset in + for item in other { + if let index = self._find_inlined(item).index { + bitset.insert(index) + } + } + let result = self._extractSubset(using: bitset) + result._checkInvariants() + return result + } + } + + /// Removes the elements of this set that aren't also in the given sequence. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// set.formIntersection([6, 4, 2, 0] as Array) + /// // set is now [2, 4] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(*n*) on average where *n* is the number of + /// elements in `other`, if `Element` implements high-quality hashing. + @inlinable + public mutating func formIntersection( + _ other: S + ) where S.Element == Element { + self = self.intersection(other) + } +} + +extension OrderedSet { + /// Returns a new set with the elements that are either in this set or in + /// `other`, but not in both. + /// + /// The result contains elements from `self` followed by elements in `other`, + /// in the same order they appeared in the original sets. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.symmetricDifference(other) // [1, 3, 6, 0] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + public __consuming func symmetricDifference(_ other: __owned Self) -> Self { + _UnsafeBitset.withTemporaryBitset(capacity: self.count) { bitset1 in + _UnsafeBitset.withTemporaryBitset(capacity: other.count) { bitset2 in + bitset1.insertAll(upTo: self.count) + for item in other { + if let index = self._find(item).index { + bitset1.remove(index) + } + } + bitset2.insertAll(upTo: other.count) + for item in self { + if let index = other._find(item).index { + bitset2.remove(index) + } + } + var result = self._extractSubset(using: bitset1, + extraCapacity: bitset2.count) + for offset in bitset2 { + result._appendNew(other._elements[offset]) + } + result._checkInvariants() + return result + } + } + } + + /// Replace this set with the elements contained in this set or the given + /// set, but not both. + /// + /// On return, `self` contains elements originally from `self` followed by + /// elements in `other`, in the same order they appeared in the input values. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.formSymmetricDifference(other) + /// // set is now [1, 3, 6, 0] + /// + /// - Parameter other: Another set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + public mutating func formSymmetricDifference(_ other: __owned Self) { + self = self.symmetricDifference(other) + } + + // Generalizations + + /// Returns a new set with the elements that are either in this set or in + /// `other`, but not in both. + /// + /// The result contains elements from `self` followed by elements in `other`, + /// in the same order they appeared in the original sets. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.symmetricDifference(other.unordered) // [1, 3, 6, 0] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public __consuming func symmetricDifference( + _ other: __owned UnorderedView + ) -> Self { + symmetricDifference(other._base) + } + + /// Replace this set with the elements contained in this set or the given + /// set, but not both. + /// + /// On return, `self` contains elements originally from `self` followed by + /// elements in `other`, in the same order they appeared in the input values. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.formSymmetricDifference(other.unordered) + /// // set is now [1, 3, 6, 0] + /// + /// - Parameter other: Another set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public mutating func formSymmetricDifference(_ other: __owned UnorderedView) { + formSymmetricDifference(other._base) + } + + /// Returns a new set with the elements that are either in this set or in + /// `other`, but not in both. + /// + /// The result contains elements from `self` followed by elements in `other`, + /// in the same order they appeared in the original input values. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// let other: Array = [6, 4, 2, 0] + /// set.symmetricDifference(other) // [1, 3, 6, 0] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average where *n* is + /// the number of elements in `other`, if `Element` implements high-quality + /// hashing. + @inlinable + public __consuming func symmetricDifference( + _ other: __owned S + ) -> Self where S.Element == Element { + _UnsafeBitset.withTemporaryBitset(capacity: self.count) { bitset in + var new = Self() + bitset.insertAll(upTo: self.count) + for item in other { + if let index = self._find(item).index { + bitset.remove(index) + } else { + new.append(item) + } + } + var result = _extractSubset(using: bitset, extraCapacity: new.count) + for item in new._elements { + result._appendNew(item) + } + result._checkInvariants() + return result + } + } + + /// Replace this set with the elements contained in this set or the given + /// sequence, but not both. + /// + /// On return, `self` contains elements originally from `self` followed by + /// elements in `other`, in the same order they first appeared in the input + /// values. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// set.formSymmetricDifference([6, 4, 2, 0] as Array) + /// // set is now [1, 3, 6, 0] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average where *n* is + /// the number of elements in `other`, if `Element` implements high-quality + /// hashing. + @inlinable + public mutating func formSymmetricDifference( + _ other: __owned S + ) where S.Element == Element { + self = self.symmetricDifference(other) + } +} + +extension OrderedSet { + /// Returns a new set containing the elements of this set that do not occur + /// in the given set. + /// + /// The result contains elements in the same order they appear in `self`. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.subtracting(other) // [1, 3] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public __consuming func subtracting(_ other: Self) -> Self { + _subtracting(other) + } + + /// Removes the elements of the given set from this set. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.subtract(other) + /// // set is now [1, 3] + /// + /// - Parameter other: Another set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public mutating func subtract(_ other: Self) { + self = subtracting(other) + } + + // Generalizations + + /// Returns a new set containing the elements of this set that do not occur + /// in the given set. + /// + /// The result contains elements in the same order they appear in `self`. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.subtracting(other.unordered) // [1, 3] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public __consuming func subtracting(_ other: UnorderedView) -> Self { + subtracting(other._base) + } + + /// Removes the elements of the given set from this set. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// let other: OrderedSet = [6, 4, 2, 0] + /// set.subtract(other.unordered) + /// // set is now [1, 3] + /// + /// - Parameter other: Another set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public mutating func subtract(_ other: UnorderedView) { + subtract(other._base) + } + + /// Returns a new set containing the elements of this set that do not occur + /// in the given sequence. + /// + /// The result contains elements in the same order they appear in `self`. + /// + /// let set: OrderedSet = [1, 2, 3, 4] + /// set.subtracting([6, 4, 2, 0] as Array) // [1, 3] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public __consuming func subtracting( + _ other: S + ) -> Self where S.Element == Element { + _subtracting(other) + } + + /// Removes the elements of the given sequence from this set. + /// + /// var set: OrderedSet = [1, 2, 3, 4] + /// set.subtract([6, 4, 2, 0] as Array) + /// // set is now [1, 3] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average, where *n* + /// is the number of elements in `other`, if `Element` implements + /// high-quality hashing. + @inlinable + @inline(__always) + public mutating func subtract( + _ other: S + ) where S.Element == Element { + self = _subtracting(other) + } + + @inlinable + __consuming func _subtracting( + _ other: S + ) -> Self where S.Element == Element { + guard count > 0 else { return Self() } + return _UnsafeBitset.withTemporaryBitset(capacity: count) { difference in + difference.insertAll(upTo: count) + for item in other { + if let index = self._find(item).index { + if difference.remove(index), difference.count == 0 { + return Self() + } + } + } + assert(difference.count > 0) + let result = _extractSubset(using: difference) + result._checkInvariants() + return result + } + } +} + + diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Predicates.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Predicates.swift new file mode 100644 index 0000000000..e975a0b4b7 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra+Predicates.swift @@ -0,0 +1,551 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +// `OrderedSet` does not directly conform to `SetAlgebra` because its definition +// of equality conflicts with `SetAlgebra` requirements. However, it still +// implements most `SetAlgebra` requirements (except `insert`, which is replaced +// by `append`). +// +// `OrderedSet` also provides an `unordered` view that explicitly conforms to +// `SetAlgebra`. That view implements `Equatable` by ignoring element order, +// so it can satisfy `SetAlgebra` requirements. + +extension OrderedSet { + /// Returns a Boolean value that indicates whether this set is a subset of + /// the given set. + /// + /// Set *A* is a subset of another set *B* if every member of *A* is also a + /// member of *B*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// b.isSubset(of: a) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isSubset(of other: Self) -> Bool { + guard other.count >= self.count else { return false } + for item in self { + guard other.contains(item) else { return false } + } + return true + } + + // Generalizations + + /// Returns a Boolean value that indicates whether this set is a subset of + /// the given set. + /// + /// Set *A* is a subset of another set *B* if every member of *A* is also a + /// member of *B*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// b.isSubset(of: a.unordered) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + @inline(__always) + public func isSubset(of other: UnorderedView) -> Bool { + isSubset(of: other._base) + } + + /// Returns a Boolean value that indicates whether this set is a subset of + /// the given set. + /// + /// Set *A* is a subset of another set *B* if every member of *A* is also a + /// member of *B*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Set = [4, 2, 1] + /// b.isSubset(of: a) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isSubset(of other: Set) -> Bool { + guard other.count >= self.count else { return false } + for item in self { + guard other.contains(item) else { return false } + } + return true + } + + /// Returns a Boolean value that indicates whether this set is a subset of + /// the elements in the given sequence. + /// + /// Set *A* is a subset of another set *B* if every member of *A* is also a + /// member of *B*, ignoring the order they appear in the two sets. + /// + /// let a: Array = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// b.isSubset(of: a) // true + /// + /// - Parameter other: A finite sequence. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average, where *n* + /// is the number of elements in `other`, if `Element` implements + /// high-quality hashing. + @inlinable + public func isSubset( + of other: S + ) -> Bool where S.Element == Element { + guard !isEmpty else { return true } + return _UnsafeBitset.withTemporaryBitset(capacity: count) { seen in + // Mark elements in `self` that we've seen in `other`. + for item in other { + if let index = _find(item).index { + if seen.insert(index), seen.count == self.count { + // We've seen enough. + return true + } + } + } + return false + } + } +} + +extension OrderedSet { + /// Returns a Boolean value that indicates whether this set is a superset of + /// the given set. + /// + /// Set *A* is a superset of another set *B* if every member of *B* is also a + /// member of *A*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// a.isSuperset(of: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a superset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isSuperset(of other: Self) -> Bool { + other.isSubset(of: self) + } + + // Generalizations + + /// Returns a Boolean value that indicates whether this set is a superset of + /// the given set. + /// + /// Set *A* is a superset of another set *B* if every member of *B* is also a + /// member of *A*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Set = [4, 2, 1] + /// a.isSuperset(of: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a superset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isSuperset(of other: UnorderedView) -> Bool { + isSuperset(of: other._base) + } + + @inlinable + public func isSuperset(of other: Set) -> Bool { + guard self.count >= other.count else { return false } + return _isSuperset(of: other) + } + + /// Returns a Boolean value that indicates whether this set is a superset of + /// the given sequence. + /// + /// Set *A* is a superset of another set *B* if every member of *B* is also a + /// member of *A*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Array = [4, 2, 1] + /// a.isSuperset(of: b) // true + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: `true` if the set is a superset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(*n*) on average, where *n* is the number of + /// elements in `other`, if `Element` implements high-quality hashing. + @inlinable + public func isSuperset( + of other: S + ) -> Bool where S.Element == Element { + _isSuperset(of: other) + } + + @inlinable + internal func _isSuperset( + of other: S + ) -> Bool where S.Element == Element { + for item in other { + guard self.contains(item) else { return false } + } + return true + } +} + +extension OrderedSet { + /// Returns a Boolean value that indicates whether the set is a strict subset + /// of the given set. + /// + /// Set *A* is a strict subset of another set *B* if every member of *A* is + /// also a member of *B* and *B* contains at least one element that is not a + /// member of *A*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// b.isStrictSubset(of: a) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict subset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isStrictSubset(of other: Self) -> Bool { + self.count < other.count && self.isSubset(of: other) + } + + // Generalizations + + /// Returns a Boolean value that indicates whether the set is a strict subset + /// of the given set. + /// + /// Set *A* is a strict subset of another set *B* if every member of *A* is + /// also a member of *B* and *B* contains at least one element that is not a + /// member of *A*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// b.isStrictSubset(of: a.unordered) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict subset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + @inline(__always) + public func isStrictSubset(of other: UnorderedView) -> Bool { + isStrictSubset(of: other._base) + } + + /// Returns a Boolean value that indicates whether the set is a strict subset + /// of the given set. + /// + /// Set *A* is a strict subset of another set *B* if every member of *A* is + /// also a member of *B* and *B* contains at least one element that is not a + /// member of *A*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: Set = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// b.isStrictSubset(of: a) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict subset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isStrictSubset(of other: Set) -> Bool { + self.count < other.count && self.isSubset(of: other) + } + + /// Returns a Boolean value that indicates whether the set is a strict subset + /// of the given sequence. + /// + /// Set *A* is a strict subset of another set *B* if every member of *A* is + /// also a member of *B* and *B* contains at least one element that is not a + /// member of *A*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: Array = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// b.isStrictSubset(of: a) // true + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: `true` if `self` is a strict subset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average, where *n* + /// is the number of elements in `other`, if `Element` implements + /// high-quality hashing. + @inlinable + public func isStrictSubset( + of other: S + ) -> Bool where S.Element == Element { + _UnsafeBitset.withTemporaryBitset(capacity: count) { seen in + // Mark elements in `self` that we've seen in `other`. + var isKnownStrict = false + for item in other { + if let index = _find(item).index { + if seen.insert(index), seen.count == self.count, isKnownStrict { + // We've seen enough. + return true + } + } else { + if !isKnownStrict, seen.count == self.count { return true } + isKnownStrict = true + } + } + return false + } + } +} + +extension OrderedSet { + /// Returns a Boolean value that indicates whether the set is a strict + /// superset of the given set. + /// + /// Set *A* is a strict superset of another set *B* if every member of *B* is + /// also a member of *A* and *A* contains at least one element that is *not* + /// a member of *B*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// a.isStrictSuperset(of: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict superset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isStrictSuperset(of other: Self) -> Bool { + self.count > other.count && other.isSubset(of: self) + } + + // Generalizations + + /// Returns a Boolean value that indicates whether the set is a strict + /// superset of the given set. + /// + /// Set *A* is a strict superset of another set *B* if every member of *B* is + /// also a member of *A* and *A* contains at least one element that is *not* + /// a member of *B*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [4, 2, 1] + /// a.isStrictSuperset(of: b.unordered) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict superset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + @inline(__always) + public func isStrictSuperset(of other: UnorderedView) -> Bool { + isStrictSuperset(of: other._base) + } + + /// Returns a Boolean value that indicates whether the set is a strict + /// superset of the given set. + /// + /// Set *A* is a strict superset of another set *B* if every member of *B* is + /// also a member of *A* and *A* contains at least one element that is *not* + /// a member of *B*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Set = [4, 2, 1] + /// a.isStrictSuperset(of: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict superset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isStrictSuperset(of other: Set) -> Bool { + self.count > other.count && other.isSubset(of: self) + } + + /// Returns a Boolean value that indicates whether the set is a strict + /// superset of the given sequence. + /// + /// Set *A* is a strict superset of another set *B* if every member of *B* is + /// also a member of *A* and *A* contains at least one element that is *not* + /// a member of *B*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Array = [4, 2, 1] + /// a.isStrictSuperset(of: b) // true + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: `true` if `self` is a strict superset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average, where *n* + /// is the number of elements in `other`, if `Element` implements + /// high-quality hashing. + @inlinable + public func isStrictSuperset( + of other: S + ) -> Bool where S.Element == Element { + _UnsafeBitset.withTemporaryBitset(capacity: count) { seen in + // Mark elements in `self` that we've seen in `other`. + for item in other { + guard let index = _find(item).index else { + return false + } + if seen.insert(index), seen.count == self.count { + // We've seen enough. + return false + } + } + return seen.count < self.count + } + } +} + +extension OrderedSet { + /// Returns a Boolean value that indicates whether the set has no members in + /// common with the given set. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [5, 6] + /// a.isDisjoint(with: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` has no elements in common with `other`; + /// otherwise, `false`. + /// + /// - Complexity: Expected to be O(min(`self.count`, `other.count`)) on + /// average, if `Element` implements high-quality hashing. + @inlinable + public func isDisjoint(with other: Self) -> Bool { + guard !self.isEmpty && !other.isEmpty else { return true } + if self.count <= other.count { + for item in self { + if other.contains(item) { return false } + } + } else { + for item in other { + if self.contains(item) { return false } + } + } + return true + } + + // Generalizations + + /// Returns a Boolean value that indicates whether the set has no members in + /// common with the given set. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [5, 6] + /// a.isDisjoint(with: b.unordered) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` has no elements in common with `other`; + /// otherwise, `false`. + /// + /// - Complexity: Expected to be O(min(`self.count`, `other.count`)) on + /// average, if `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public func isDisjoint(with other: UnorderedView) -> Bool { + isDisjoint(with: other._base) + } + + /// Returns a Boolean value that indicates whether the set has no members in + /// common with the given set. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Set = [5, 6] + /// a.isDisjoint(with: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` has no elements in common with `other`; + /// otherwise, `false`. + /// + /// - Complexity: Expected to be O(min(`self.count`, `other.count`)) on + /// average, if `Element` implements high-quality hashing. + @inlinable + public func isDisjoint(with other: Set) -> Bool { + guard !self.isEmpty && !other.isEmpty else { return true } + if self.count <= other.count { + for item in self { + if other.contains(item) { return false } + } + } else { + for item in other { + if self.contains(item) { return false } + } + } + return true + } + + /// Returns a Boolean value that indicates whether the set has no members in + /// common with the given sequence. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: Array = [5, 6] + /// a.isDisjoint(with: b) // true + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: `true` if `self` has no elements in common with `other`; + /// otherwise, `false`. + /// + /// - Complexity: Expected to be O(*n*) on average, where *n* is the number of + /// elements in `other`, if `Element` implements high-quality hashing. + @inlinable + public func isDisjoint( + with other: S + ) -> Bool where S.Element == Element { + guard !self.isEmpty else { return true } + for item in other { + if self.contains(item) { return false } + } + return true + } +} + diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+RandomAccessCollection.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+RandomAccessCollection.swift new file mode 100644 index 0000000000..7e5eed857b --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+RandomAccessCollection.swift @@ -0,0 +1,300 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet: Sequence { + /// The type that allows iteration over an ordered set's elements. + public typealias Iterator = IndexingIterator + + @inlinable + public func _customContainsEquatableElement(_ element: Element) -> Bool? { + _find(element).index != nil + } + + @inlinable + public __consuming func _copyToContiguousArray() -> ContiguousArray { + _elements._copyToContiguousArray() + } + + @inlinable + public __consuming func _copyContents( + initializing ptr: UnsafeMutableBufferPointer + ) -> (Iterator, UnsafeMutableBufferPointer.Index) { + guard !isEmpty else { return (makeIterator(), 0) } + let copied: Int = _elements.withUnsafeBufferPointer { buffer in + guard let p = ptr.baseAddress else { + preconditionFailure("Attempt to copy contents into nil buffer pointer") + } + let c = Swift.min(buffer.count, ptr.count) + p.initialize(from: buffer.baseAddress!, count: c) + return c + } + return (Iterator(_elements: self, _position: copied), copied) + } + + /// Call `body(p)`, where `p` is a buffer pointer to the collection’s + /// contiguous storage. Ordered sets always have contiguous storage. + /// + /// - Parameter body: A function to call. The function must not escape its + /// unsafe buffer pointer argument. + /// + /// - Returns: The value returned by `body`. + /// + /// - Complexity: O(1) (ignoring time spent in `body`) + @inlinable + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + try _elements.withContiguousStorageIfAvailable(body) + } +} + +extension OrderedSet: RandomAccessCollection { + /// The index type for ordered sets, `Int`. + /// + /// `OrderedSet` indices are integer offsets from the start of the collection, + /// starting at zero for the first element (if exists). + public typealias Index = Int + + /// The type that represents the indices that are valid for subscripting an + /// ordered set, in ascending order. + public typealias Indices = Range + + // For SubSequence, see OrderedSet+SubSequence.swift. + + /// The position of the first element in a nonempty ordered set. + /// + /// For an instance of `OrderedSet`, `startIndex` is always zero. If the set + /// is empty, `startIndex` is equal to `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { _elements.startIndex } + + /// The set's "past the end" position---that is, the position one greater + /// than the last valid subscript argument. + /// + /// In an `OrderedSet`, `endIndex` always equals the count of elements. + /// If the set is empty, `endIndex` is equal to `startIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { _elements.endIndex } + + /// The indices that are valid for subscripting the collection, in ascending + /// order. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var indices: Indices { _elements.indices } + + /// Returns the position immediately after the given index. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { i + 1 } + + /// Returns the position immediately before the given index. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { i - 1 } + + /// Replaces the given index with its successor. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { i += 1 } + + /// Replaces the given index with its predecessor. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { i -= 1 } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, or the returned value will not be a valid index. + /// + /// - Parameters: + /// - i: A valid index of the set. + /// - distance: The distance to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls to + /// `index(after:)`. If `distance` is negative, this is the same value as + /// the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, unless the index passed as `limit` prevents offsetting + /// beyond those bounds. (Otherwise the returned value won't be a valid index + /// in the set.) + /// + /// - Parameters: + /// - i: A valid index of the set. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the collection to use as a limit. If + /// `distance > 0`, `limit` has no effect if it is less than `i`. + /// Likewise, if `distance < 0`, `limit` has no effect if it is greater + /// than `i`. + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + _elements.index(i, offsetBy: distance, limitedBy: limit) + } + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. If `end` is equal to + /// `start`, the result is zero. + /// + /// - Returns: The distance between `start` and `end`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + end - start + } + + /// Accesses the element at the specified position. + /// + /// - Parameter index: The position of the element to access. `index` must be + /// greater than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public subscript(position: Int) -> Element { + _elements[position] + } + + /// Accesses a contiguous subrange of the set's elements. + /// + /// The returned `Subsequence` instance uses the same indices for the same + /// elements as the original set. In particular, that slice, unlike an + /// `OrderedSet`, may have a nonzero `startIndex` and an `endIndex` that is + /// not equal to `count`. Always use the slice's `startIndex` and `endIndex` + /// properties instead of assuming that its indices start or end at a + /// particular value. + /// + /// - Parameter bounds: A range of valid indices in the set. + /// + /// - Complexity: O(1) + @inlinable + public subscript(bounds: Range) -> SubSequence { + _failEarlyRangeCheck(bounds, bounds: startIndex ..< endIndex) + return SubSequence(_base: self, bounds: bounds) + } + + /// A Boolean value indicating whether the collection is empty. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var isEmpty: Bool { _elements.isEmpty } + + /// The number of elements in the set. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _elements.count } + + @inlinable + public func _customIndexOfEquatableElement(_ element: Element) -> Int?? { + guard let table = _table else { + return _elements._customIndexOfEquatableElement(element) + } + return table.read { hashTable in + let (o, _) = hashTable._find(element, in: _elements) + guard let offset = o else { return .some(nil) } + return offset + } + } + + @inlinable + @inline(__always) + public func _customLastIndexOfEquatableElement(_ element: Element) -> Int?? { + // OrderedSet holds unique elements. + _customIndexOfEquatableElement(element) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ index: Int, bounds: Range) { + _elements._failEarlyRangeCheck(index, bounds: bounds) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ index: Int, bounds: ClosedRange) { + _elements._failEarlyRangeCheck(index, bounds: bounds) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ range: Range, bounds: Range) { + _elements._failEarlyRangeCheck(range, bounds: bounds) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+ReserveCapacity.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+ReserveCapacity.swift new file mode 100644 index 0000000000..053de8cf04 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+ReserveCapacity.swift @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet { + /// Creates an empty set with preallocated space for at least the + /// specified number of elements. + /// + /// Use this initializer to avoid intermediate reallocations of a + /// set's storage buffer when you know in advance how many elements + /// you'll insert into the set after creation. + /// + /// If you have a good idea of the expected working size of the set, calling + /// this initializer with `persistent` set to true can sometimes improve + /// performance by eliminating churn due to repeated rehashings when the set + /// temporarily shrinks below its regular size. + /// + /// - Parameter minimumCapacity: The minimum number of elements that the newly + /// created set should be able to store without reallocating its storage. + /// + /// - Parameter persistent: If set to true, prevent removals from shrinking + /// storage below the specified capacity. By default, removals are allowed + /// to shrink storage below any previously reserved capacity. + /// + /// - Complexity: O(`minimumCapacity`) + @inlinable + public init(minimumCapacity: Int, persistent: Bool = false) { + self.init() + self._reserveCapacity(minimumCapacity, persistent: persistent) + } +} + +extension OrderedSet { + /// Reserves enough space to store the specified number of elements. + /// + /// This method ensures that the set has unique mutable storage, with space + /// allocated for at least the requested number of elements. + /// + /// If you are adding a known number of elements to a set, call this method + /// once before the first insertion to avoid multiple reallocations. + /// + /// Do not call this method in a loop -- it does not use an exponential + /// allocation strategy, so doing that can result in quadratic instead of + /// linear performance. + /// + /// - Parameter minimumCapacity: The minimum number of elements that the set + /// should be able to store without reallocating its storage. + /// + /// - Complexity: O(`max(count, minimumCapacity)`) + @inlinable + public mutating func reserveCapacity(_ minimumCapacity: Int) { + self._reserveCapacity(minimumCapacity, persistent: false) + } +} + +extension OrderedSet { + /// Reserves enough space to store the specified number of elements. + /// + /// This method ensures that the set has unique mutable storage, with space + /// allocated for at least the requested number of elements. + /// + /// If you are adding a known number of elements to a set, call this method + /// once before the first insertion to avoid multiple reallocations. + /// + /// Do not call this method in a loop -- it does not use an exponential + /// allocation strategy, so doing that can result in quadratic instead of + /// linear performance. + /// + /// If you have a good idea of the expected working size of the set, calling + /// this method with `persistent` set to true can sometimes improve + /// performance by eliminating churn due to repeated rehashings when the set + /// temporarily shrinks below its regular size. You can cancel any capacity + /// you've previously reserved by persistently reserving a capacity of zero. + /// (This also shrinks the hash table to the ideal size for its current number + /// elements.) + /// + /// - Parameter minimumCapacity: The minimum number of elements that the set + /// should be able to store without reallocating its storage. + /// + /// - Parameter persistent: If set to true, prevent removals from shrinking + /// storage below the specified capacity. By default, removals are allowed + /// to shrink storage below any previously reserved capacity. + /// + /// - Complexity: O(`max(count, minimumCapacity)`) + @inlinable + internal mutating func _reserveCapacity( + _ minimumCapacity: Int, + persistent: Bool + ) { + precondition(minimumCapacity >= 0, "Minimum capacity cannot be negative") + defer { _checkInvariants() } + + _elements.reserveCapacity(minimumCapacity) + + let currentScale = _scale + let newScale = _HashTable.scale(forCapacity: minimumCapacity) + + let reservedScale = persistent ? newScale : _reservedScale + + if currentScale < newScale { + // Grow the table. + _regenerateHashTable(scale: newScale, reservedScale: reservedScale) + return + } + + let requiredScale = _HashTable.scale(forCapacity: self.count) + let minScale = Swift.max(Swift.max(newScale, reservedScale), requiredScale) + if minScale < currentScale { + // Shrink the table. + _regenerateHashTable(scale: minScale, reservedScale: reservedScale) + return + } + + // When we have the right size table, ensure it's unique and it has the + // right persisted reservation. + _ensureUnique() + if _reservedScale != reservedScale { + // Remember reserved scale. + __storage!.header.reservedScale = reservedScale + } + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+SubSequence.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+SubSequence.swift new file mode 100644 index 0000000000..706c8f99c7 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+SubSequence.swift @@ -0,0 +1,355 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet { + /// A collection that represents a contiguous slice of an ordered set. + /// + /// Ordered set slices are random access collections that support efficient + /// membership testing. `contains(_:)` and `firstIndex(of:)`/`lastIndex(of:)` + /// are expected to have a complexity of O(1), as long as `Element` has + /// high-quality hashing. + @frozen + public struct SubSequence { + @usableFromInline + internal var _base: OrderedSet + + @usableFromInline + internal var _bounds: Range + + @inlinable + @inline(__always) + internal init(_base: OrderedSet, bounds: Range) { + self._base = _base + self._bounds = bounds + } + } +} + +extension OrderedSet.SubSequence { + @inlinable + internal var _slice: Array.SubSequence { + _base._elements[_bounds] + } + + @inlinable + internal func _index(of element: Element) -> Int? { + guard let index = _base._find(element).index else { return nil } + guard _bounds.contains(index) else { return nil } + return index + } +} + +extension OrderedSet.SubSequence: Sequence { + // A type representing the collection’s elements. + public typealias Element = OrderedSet.Element + /// The type that allows iteration over the collection's elements. + public typealias Iterator = IndexingIterator + + @inlinable + public func _customContainsEquatableElement(_ element: Element) -> Bool? { + _index(of: element) != nil + } + + @inlinable + public __consuming func _copyToContiguousArray() -> ContiguousArray { + _slice._copyToContiguousArray() + } + + @inlinable + public __consuming func _copyContents( + initializing ptr: UnsafeMutableBufferPointer + ) -> (Iterator, UnsafeMutableBufferPointer.Index) { + guard !isEmpty else { return (makeIterator(), 0) } + let copied: Int = _slice.withUnsafeBufferPointer { buffer in + guard let p = ptr.baseAddress else { + preconditionFailure("Attempt to copy contents into nil buffer pointer") + } + let c = Swift.min(buffer.count, ptr.count) + if c > 0 { + p.initialize(from: buffer.baseAddress!, count: c) + } + return c + } + return (Iterator(_elements: self, _position: _bounds.lowerBound + copied), + copied) + } + + /// Call `body(p)`, where `p` is a buffer pointer to the collection’s + /// contiguous storage. Ordered sets always have contiguous storage. + /// + /// - Parameter body: A function to call. The function must not escape its + /// unsafe buffer pointer argument. + /// + /// - Returns: The value returned by `body`. + /// + /// - Complexity: O(1) (ignoring time spent in `body`) + @inlinable + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + try _slice.withContiguousStorageIfAvailable(body) + } +} + +extension OrderedSet.SubSequence: RandomAccessCollection { + /// The index type for ordered sets, `Int`. + /// + /// Indices in the order set are integer offsets from the start of the + /// collection, starting at zero for the first element (if exists). + public typealias Index = Int + + /// The type that represents the indices that are valid for subscripting an + /// ordered set, in ascending order. + public typealias Indices = Array.SubSequence.Indices + + /// Ordered set subsequences are self-slicing. + public typealias SubSequence = Self + + /// The position of the first element in a nonempty ordered set slice. + /// + /// Note that instances of `OrderedSet.SubSequence` generally aren't indexed + /// from zero. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { _bounds.lowerBound } + + /// The "past the end" position---that is, the position one greater + /// than the last valid subscript argument. + /// + /// Note that instances of `OrderedSet.SubSequence` generally aren't indexed + /// from zero, so `endIndex` may differ from the `count`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { _bounds.upperBound } + + /// The indices that are valid for subscripting the collection, in ascending + /// order. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var indices: Indices { _slice.indices } + + /// Returns the position immediately after the given index. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { i + 1 } + + /// Returns the position immediately before the given index. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { i - 1 } + + /// Replaces the given index with its successor. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { i += 1 } + + /// Replaces the given index with its predecessor. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { i -= 1 } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, or the returned value will not be a valid index. + /// + /// - Parameters: + /// - i: A valid index of the set. + /// - distance: The distance to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls to + /// `index(after:)`. If `distance` is negative, this is the same value as + /// the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, unless the index passed as `limit` prevents offsetting + /// beyond those bounds. (Otherwise the returned value won't be a valid index + /// in the set.) + /// + /// - Parameters: + /// - i: A valid index of the set. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the collection to use as a limit. If + /// `distance > 0`, `limit` has no effect if it is less than `i`. + /// Likewise, if `distance < 0`, `limit` has no effect if it is greater + /// than `i`. + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + _slice.index(i, offsetBy: distance, limitedBy: limit) + } + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. If `end` is equal to + /// `start`, the result is zero. + /// + /// - Returns: The distance between `start` and `end`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + end - start + } + + /// Accesses the element at the specified position. + /// + /// - Parameter index: The position of the element to access. `index` must be + /// greater than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public subscript(position: Int) -> Element { + _slice[position] + } + + /// Accesses a contiguous subrange of the set's elements. + /// + /// The returned `Subsequence` instance uses the same indices for the same + /// elements as the original set. In particular, that slice, unlike an + /// `OrderedSet`, may have a nonzero `startIndex` and an `endIndex` that is + /// not equal to `count`. Always use the slice's `startIndex` and `endIndex` + /// properties instead of assuming that its indices start or end at a + /// particular value. + /// + /// - Parameter bounds: A range of valid indices in the set. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public subscript(bounds: Range) -> SubSequence { + _failEarlyRangeCheck(bounds, bounds: startIndex ..< endIndex) + return SubSequence(_base: _base, bounds: bounds) + } + + /// A Boolean value indicating whether the collection is empty. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var isEmpty: Bool { _bounds.isEmpty } + + /// The number of elements in the set. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _bounds.count } + + @inlinable + @inline(__always) + public func _customIndexOfEquatableElement(_ element: Element) -> Int?? { + .some(_index(of: element)) + } + + @inlinable + @inline(__always) + public func _customLastIndexOfEquatableElement(_ element: Element) -> Int?? { + .some(_index(of: element)) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ index: Int, bounds: Range) { + _slice._failEarlyRangeCheck(index, bounds: bounds) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ index: Int, bounds: ClosedRange) { + _slice._failEarlyRangeCheck(index, bounds: bounds) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ range: Range, bounds: Range) { + _slice._failEarlyRangeCheck(range, bounds: bounds) + } +} + +extension OrderedSet.SubSequence: Equatable { + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + left.elementsEqual(right) + } +} + +extension OrderedSet.SubSequence: Hashable { + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(count) + for item in self { + hasher.combine(item) + } + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+UnorderedView.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+UnorderedView.swift new file mode 100644 index 0000000000..88216a6d53 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+UnorderedView.swift @@ -0,0 +1,987 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet { + /// An unordered view into an ordered set, providing `SetAlgebra` + /// conformance. + @frozen + public struct UnorderedView { + @usableFromInline + internal var _base: OrderedSet + + @inlinable + @inline(__always) + internal init(_base: OrderedSet) { + self._base = _base + } + } + + /// Create a new ordered set with the same members as the supplied + /// unordered view. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public init(_ view: UnorderedView) { + self = view._base + } + + /// Access a view of the members of this set as an unordered + /// `SetAlgebra` value. + /// + /// This is useful when you need to pass an ordered set to a + /// function that is generic over `SetAlgebra`. + /// + /// The unordered view has a definition of equality that ignores the + /// order of members, so that it can satisfy `SetAlgebra` + /// requirements. New elements inserted to the unordered view get + /// appended to the end of the set. + /// + /// - Complexity: O(1) for both the getter and the setter. + @inlinable + public var unordered: UnorderedView { + @inline(__always) + get { + UnorderedView(_base: self) + } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + var view = UnorderedView(_base: self) + self = OrderedSet() + defer { self = view._base } + yield &view + } + } +} + +extension OrderedSet.UnorderedView: CustomStringConvertible { + /// A textual representation of this instance. + public var description: String { + _base.description + } +} + +extension OrderedSet.UnorderedView: CustomDebugStringConvertible { + /// A textual representation of this instance, suitable for debugging. + public var debugDescription: String { + _base._debugDescription(typeName: "\(_base._debugTypeName()).UnorderedView") + } +} + +extension OrderedSet.UnorderedView: CustomReflectable { + /// The custom mirror for this instance. + public var customMirror: Mirror { + Mirror(self, unlabeledChildren: _base._elements, displayStyle: .collection) + } +} + +extension OrderedSet.UnorderedView: Equatable { + /// Returns a Boolean value indicating whether two values are equal. + /// Two unordered sets are considered equal if they contain the same + /// elements, but not necessarily in the same order. + /// + /// - Complexity: O(`min(left.count, right.count)`) + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + if left._base.__storage != nil, + left._base.__storage === right._base.__storage + { + return true + } + guard left._base.count == right._base.count else { return false } + + for item in left._base { + if !right._base.contains(item) { return false } + } + return true + } +} + +extension OrderedSet.UnorderedView: Hashable { + /// Hashes the essential components of this value by feeding them into the + /// given hasher. + /// + /// Complexity: O(`count`) + @inlinable + public func hash(into hasher: inout Hasher) { + // Generate a seed from a snapshot of the hasher. This makes members' hash + // values depend on the state of the hasher, which improves hashing + // quality. (E.g., it makes it possible to resolve collisions by passing in + // a different hasher.) + let copy = hasher + let seed = copy.finalize() + + var hash = 0 + for member in _base { + hash ^= member._rawHashValue(seed: seed) + } + hasher.combine(hash) + } +} + +extension OrderedSet.UnorderedView: ExpressibleByArrayLiteral { + /// Creates a new unordered set from the contents of an array literal. + @inlinable + @inline(__always) + public init(arrayLiteral elements: Element...) { + _base = OrderedSet(elements) + } +} + +extension OrderedSet.UnorderedView: SetAlgebra { + public typealias Element = OrderedSet.Element +} + +extension OrderedSet.UnorderedView { + /// Creates an empty set. + /// + /// This initializer is equivalent to initializing with an empty array + /// literal. + @inlinable + @inline(__always) + public init() { + _base = OrderedSet() + } + + /// Creates a new set from a finite sequence of items. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: This operation is expected to perform O(*n*) + /// comparisons on average (where *n* is the number of elements + /// in the sequence), provided that `Element` implements + /// high-quality hashing. + @inlinable + @inline(__always) + public init(_ elements: S) where S.Element == Element { + _base = OrderedSet(elements) + } + + // Specializations + + /// Creates a new set from a an existing set. This is functionally the same as + /// copying the value of `elements` into a new variable. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public init(_ elements: Self) { + self = elements + } + + /// Creates a new set from an existing `Set` value. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: This operation is expected to perform O(*n*) + /// comparisons on average (where *n* is the number of elements + /// in the set), provided that `Element` implements high-quality + /// hashing. + @inlinable + @inline(__always) + public init(_ elements: Set) { + self._base = OrderedSet(elements) + } + + /// Creates a new set from the keys of a dictionary value. + /// + /// - Parameter elements: The elements to use as members of the new set. + /// + /// - Complexity: This operation is expected to perform O(*n*) + /// comparisons on average (where *n* is the number of elements + /// in the set), provided that `Element` implements high-quality + /// hashing. + @inlinable + @inline(__always) + public init(_ elements: Dictionary.Keys) { + self._base = OrderedSet(elements) + } +} + +extension OrderedSet.UnorderedView { + /// Returns a Boolean value that indicates whether the given element exists + /// in the set. + /// + /// - Parameter element: An element to look for in the set. + /// + /// - Returns: `true` if `member` exists in the set; otherwise, `false`. + /// + /// - Complexity: This operation is expected to perform O(1) comparisons on + /// average, provided that `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public func contains(_ element: Element) -> Bool { + _base.contains(element) + } +} + +extension OrderedSet.UnorderedView { + /// Inserts the given element in the set if it is not already present. + /// + /// If an element equal to `newMember` is already contained in the set, this + /// method has no effect. + /// + /// If `newMember` was not already a member, it gets appended to the end of + /// the underlying ordered set value. + /// + /// - Parameter newMember: An element to insert into the set. + /// + /// - Returns: `(true, newMember)` if `newMember` was not contained in the + /// set. If an element equal to `newMember` was already contained in the + /// set, the method returns `(false, oldMember)`, where `oldMember` is the + /// element that was equal to `newMember`. In some cases, `oldMember` may + /// be distinguishable from `newMember` by identity comparison or some + /// other means. + /// + /// - Complexity: This operation is expected to perform O(1) + /// hashing/comparison operations on average (over many insertions to the + /// same set), provided that `Element` implements high-quality hashing. + @inlinable + public mutating func insert( + _ newMember: __owned Element + ) -> (inserted: Bool, memberAfterInsert: Element) { + let (inserted, index) = _base.append(newMember) + return (inserted, _base[index]) + } + + /// Inserts the given element into the set unconditionally. + /// + /// If an element equal to `newMember` is already contained in the set, + /// `newMember` replaces the existing element. + /// + /// If `newMember` was not already a member, it gets appended to the end of + /// the underlying ordered set value. + /// + /// - Parameter newMember: An element to insert into the set. + /// + /// - Returns: The original member equal to `newMember` if the set already + /// contained such a member; otherwise, `nil`. In some cases, the returned + /// element may be distinguishable from `newMember` by identity comparison + /// or some other means. + /// + /// - Complexity: This operation is expected to perform O(1) + /// hashing/comparison operations on average (over many insertions to the + /// same set), provided that `Element` implements high-quality hashing. + @inlinable + public mutating func update(with newMember: __owned Element) -> Element? { + let (inserted, index) = _base.append(newMember) + if inserted { return nil } + let old = _base._elements[index] + _base._elements[index] = newMember + return old + } +} + +extension OrderedSet.UnorderedView { + /// Removes the given element from the set. + /// + /// - Parameter member: The element of the set to remove. + /// + /// - Returns: The element equal to `member` if `member` is contained in the + /// set; otherwise, `nil`. In some cases, the returned element may be + /// distinguishable from `newMember` by identity comparison or some other + /// means. + /// + /// - Complexity: O(`count`). Removing an element from the middle of the + /// underlying ordered set needs to rearrange the remaining elements to + /// close the resulting gap. + /// + /// Removing the last element only takes (amortized) O(1) + /// hashing/comparisons operations, if `Element` implements high quality + /// hashing. + @inlinable + @inline(__always) + @discardableResult + public mutating func remove(_ member: Element) -> Element? { + _base.remove(member) + } +} + +extension OrderedSet.UnorderedView { + /// Adds the elements of the given set to this set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the set, in the order they appear in `other`. + /// + /// var set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let other: OrderedSet.UnorderedView = [0, 2, 4, 6] + /// set.formUnion(other) + /// // `set` is now `[1, 2, 3, 4, 0, 6]` + /// + /// - Parameter other: The set of elements to insert. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + @inline(__always) + public mutating func formUnion(_ other: __owned Self) { + _base.formUnion(other._base) + } + + /// Returns a new set with the elements of both this and the given set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the result, in the order they appear in `other`. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [0, 2, 4, 6] + /// a.union(b) // [1, 2, 3, 4, 0, 6] + /// + /// - Parameter other: The set of elements to add. + /// + /// - Complexity: Expected to be O(`self.count` + `other.count`) on average, + /// if `Element` implements high-quality hashing. + @inlinable + public __consuming func union(_ other: __owned Self) -> Self { + _base.union(other._base).unordered + } + + // Generalizations + + /// Adds the elements of the given sequence to this set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the set, in the order they appear in `other`. + /// + /// var set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// set.formUnion([0, 2, 4, 6]) + /// // `set` is now `[1, 2, 3, 4, 0, 6]` + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public mutating func formUnion( + _ other: __owned S + ) where S.Element == Element { + _base.formUnion(other) + } + + /// Returns a new set with the elements of both this and the given set. + /// + /// Members of `other` that aren't already in `self` get appended to the end + /// of the result, in the order they appear in `other`. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// a.union([0, 2, 4, 6]) // [1, 2, 3, 4, 0, 6] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(`self.count` + `other.count`) on average, + /// if `Element` implements high-quality hashing. + @inlinable + public __consuming func union( + _ other: __owned S + ) -> Self where S.Element == Element { + _base.union(other).unordered + } +} + +extension OrderedSet.UnorderedView { + /// Returns a new set with the elements that are common to both this set and + /// the provided other one, in the order they appear in `self`. + /// + /// let set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let other: OrderedSet.UnorderedView = [6, 4, 2, 0] + /// set.intersection(other) // [2, 4] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public __consuming func intersection(_ other: Self) -> Self { + _base.intersection(other._base).unordered + } + + /// Removes the elements of this set that aren't also in the given one. + /// + /// var set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let other: OrderedSet.UnorderedView = [6, 4, 2, 0] + /// set.formIntersection(other) + /// // set is now [2, 4] + /// + /// - Parameter other: A set of elements. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public mutating func formIntersection(_ other: Self) { + _base.formIntersection(other._base) + } + + // Generalizations + + /// Returns a new set with the elements that are common to both this set and + /// the provided sequence, in the order they appear in `self`. + /// + /// let set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// set.intersection([6, 4, 2, 0] as Array) // [2, 4] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(*n*) on average where *n* is the number of + /// elements in `other`, if `Element` implements high-quality hashing. + @inlinable + public __consuming func intersection( + _ other: S + ) -> Self where S.Element == Element { + _base.intersection(other).unordered + } + + /// Removes the elements of this set that aren't also in the given sequence. + /// + /// var set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// set.formIntersection([6, 4, 2, 0] as Array) + /// // set is now [2, 4] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(*n*) on average where *n* is the number of + /// elements in `other`, if `Element` implements high-quality hashing. + @inlinable + public mutating func formIntersection( + _ other: S + ) where S.Element == Element { + _base.formIntersection(other) + } +} + +extension OrderedSet.UnorderedView { + /// Returns a new set with the elements that are either in this set or in + /// `other`, but not in both. + /// + /// The result contains elements from `self` followed by elements in `other`, + /// in the same order they appeared in the original sets. + /// + /// let set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let other: OrderedSet.UnorderedView = [6, 4, 2, 0] + /// set.symmetricDifference(other) // [1, 3, 6, 0] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + public __consuming func symmetricDifference(_ other: __owned Self) -> Self { + _base.symmetricDifference(other._base).unordered + } + + /// Replace this set with the elements contained in this set or the given + /// set, but not both. + /// + /// On return, `self` contains elements originally from `self` followed by + /// elements in `other`, in the same order they appeared in the input values. + /// + /// var set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let other: OrderedSet.UnorderedView = [6, 4, 2, 0] + /// set.formSymmetricDifference(other) + /// // set is now [1, 3, 6, 0] + /// + /// - Parameter other: Another set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + public mutating func formSymmetricDifference(_ other: __owned Self) { + _base.formSymmetricDifference(other._base) + } + + // Generalizations + + /// Returns a new set with the elements that are either in this set or in the + /// given sequence, but not in both. + /// + /// The result contains elements from `self` followed by elements in `other`, + /// in the same order they first appeared in the input values. + /// + /// let set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// set.symmetricDifference([6, 4, 2, 0] as Array) // [1, 3, 6, 0] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average where *n* is + /// the number of elements in `other`, if `Element` implements high-quality + /// hashing. + @inlinable + public __consuming func symmetricDifference( + _ other: __owned S + ) -> Self where S.Element == Element { + _base.symmetricDifference(other).unordered + } + + /// Replace this set with the elements contained in this set or the given + /// sequence, but not both. + /// + /// On return, `self` contains elements originally from `self` followed by + /// elements in `other`, in the same order they first appeared in the input + /// values. + /// + /// var set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// set.formSymmetricDifference([6, 4, 2, 0] as Array) + /// // set is now [1, 3, 6, 0] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average where *n* is + /// the number of elements in `other`, if `Element` implements high-quality + /// hashing. + @inlinable + public mutating func formSymmetricDifference( + _ other: __owned S + ) where S.Element == Element { + _base.formSymmetricDifference(other) + } +} + +extension OrderedSet.UnorderedView { + /// Returns a new set containing the elements of this set that do not occur + /// in the given set. + /// + /// The result contains elements in the same order they appear in `self`. + /// + /// let set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let other: OrderedSet.UnorderedView = [6, 4, 2, 0] + /// set.subtracting(other) // [1, 3] + /// + /// - Parameter other: Another set. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + public __consuming func subtracting(_ other: Self) -> Self { + _base.subtracting(other._base).unordered + } + + /// Removes the elements of the given set from this set. + /// + /// var set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let other: OrderedSet.UnorderedView = [6, 4, 2, 0] + /// set.subtract(other) + /// // set is now [1, 3] + /// + /// - Parameter other: Another set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + public mutating func subtract(_ other: Self) { + _base.subtract(other._base) + } + + // Generalizations + + /// Returns a new set containing the elements of this set that do not occur + /// in the given sequence. + /// + /// The result contains elements in the same order they appear in `self`. + /// + /// let set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// set.subtracting([6, 4, 2, 0] as Array) // [1, 3] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: A new set. + /// + /// - Complexity: Expected to be O(`self.count + other.count`) on average, if + /// `Element` implements high-quality hashing. + @inlinable + public __consuming func subtracting( + _ other: S + ) -> Self where S.Element == Element { + _base.subtracting(other).unordered + } + + /// Removes the elements of the given sequence from this set. + /// + /// var set: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// set.subtract([6, 4, 2, 0] as Array) + /// // set is now [1, 3] + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average, where *n* + /// is the number of elements in `other`, if `Element` implements + /// high-quality hashing. + @inlinable + public mutating func subtract( + _ other: S + ) where S.Element == Element { + _base.subtract(other) + } +} + +extension OrderedSet.UnorderedView { + /// Returns a Boolean value that indicates whether this set is a subset of + /// the given set. + /// + /// Set *A* is a subset of another set *B* if every member of *A* is also a + /// member of *B*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [4, 2, 1] + /// b.isSubset(of: a) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isSubset(of other: Self) -> Bool { + _base.isSubset(of: other._base) + } + + // Generalizations + + /// Returns a Boolean value that indicates whether this set is a subset of + /// the given set. + /// + /// Set *A* is a subset of another set *B* if every member of *A* is also a + /// member of *B*, ignoring the order they appear in the two sets. + /// + /// let a: Set = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [4, 2, 1] + /// b.isSubset(of: a) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isSubset(of other: Set) -> Bool { + _base.isSubset(of: other) + } + + /// Returns a Boolean value that indicates whether this set is a subset of + /// the elements in the given sequence. + /// + /// Set *A* is a subset of another set *B* if every member of *A* is also a + /// member of *B*, ignoring the order they appear in the two sets. + /// + /// let a: Array = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [4, 2, 1] + /// b.isSubset(of: a) // true + /// + /// - Parameter other: A finite sequence. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average, where *n* + /// is the number of elements in `other`, if `Element` implements + /// high-quality hashing. + @inlinable + public func isSubset( + of other: S + ) -> Bool where S.Element == Element { + _base.isSubset(of: other) + } +} + +extension OrderedSet.UnorderedView { + /// Returns a Boolean value that indicates whether this set is a superset of + /// the given set. + /// + /// Set *A* is a superset of another set *B* if every member of *B* is also a + /// member of *A*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [4, 2, 1] + /// a.isSuperset(of: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isSuperset(of other: Self) -> Bool { + _base.isSuperset(of: other._base) + } + + // Generalizations + + /// Returns a Boolean value that indicates whether this set is a superset of + /// the given set. + /// + /// Set *A* is a superset of another set *B* if every member of *B* is also a + /// member of *A*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: Set = [4, 2, 1] + /// a.isSuperset(of: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isSuperset(of other: Set) -> Bool { + _base.isSuperset(of: other) + } + + /// Returns a Boolean value that indicates whether this set is a superset of + /// the given sequence. + /// + /// Set *A* is a superset of another set *B* if every member of *B* is also a + /// member of *A*, ignoring the order they appear in the two sets. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: Array = [4, 2, 1] + /// a.isSuperset(of: b) // true + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: `true` if the set is a subset of `other`; otherwise, `false`. + /// + /// - Complexity: Expected to be O(*n*) on average, where *n* is the number of + /// elements in `other`, if `Element` implements high-quality hashing. + @inlinable + public func isSuperset( + of other: S + ) -> Bool where S.Element == Element { + _base.isSuperset(of: other) + } +} + +extension OrderedSet.UnorderedView { + /// Returns a Boolean value that indicates whether the set is a strict subset + /// of the given set. + /// + /// Set *A* is a strict subset of another set *B* if every member of *A* is + /// also a member of *B* and *B* contains at least one element that is not a + /// member of *A*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [4, 2, 1] + /// b.isStrictSubset(of: a) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict subset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isStrictSubset(of other: Self) -> Bool { + _base.isStrictSubset(of: other._base) + } + + // Generalizations + + /// Returns a Boolean value that indicates whether the set is a strict subset + /// of the given set. + /// + /// Set *A* is a strict subset of another set *B* if every member of *A* is + /// also a member of *B* and *B* contains at least one element that is not a + /// member of *A*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: Set = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [4, 2, 1] + /// b.isStrictSubset(of: a) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict subset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isStrictSubset(of other: Set) -> Bool { + _base.isStrictSubset(of: other) + } + + /// Returns a Boolean value that indicates whether the set is a strict subset + /// of the given sequence. + /// + /// Set *A* is a strict subset of another set *B* if every member of *A* is + /// also a member of *B* and *B* contains at least one element that is not a + /// member of *A*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: Array = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [4, 2, 1] + /// b.isStrictSubset(of: a) // true + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: `true` if `self` is a strict subset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average, where *n* + /// is the number of elements in `other`, if `Element` implements + /// high-quality hashing. + @inlinable + public func isStrictSubset( + of other: S + ) -> Bool where S.Element == Element { + _base.isStrictSubset(of: other) + } +} + +extension OrderedSet.UnorderedView { + /// Returns a Boolean value that indicates whether the set is a strict + /// superset of the given set. + /// + /// Set *A* is a strict superset of another set *B* if every member of *B* is + /// also a member of *A* and *A* contains at least one element that is *not* + /// a member of *B*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [4, 2, 1] + /// a.isStrictSuperset(of: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict superset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isStrictSuperset(of other: Self) -> Bool { + _base.isStrictSuperset(of: other._base) + } + + // Generalizations + + /// Returns a Boolean value that indicates whether the set is a strict + /// superset of the given set. + /// + /// Set *A* is a strict superset of another set *B* if every member of *B* is + /// also a member of *A* and *A* contains at least one element that is *not* + /// a member of *B*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: Set = [4, 2, 1] + /// a.isStrictSuperset(of: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` is a strict superset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`other.count`) on average, if `Element` + /// implements high-quality hashing. + @inlinable + public func isStrictSuperset(of other: Set) -> Bool { + _base.isStrictSuperset(of: other) + } + + /// Returns a Boolean value that indicates whether the set is a strict + /// superset of the given sequence. + /// + /// Set *A* is a strict superset of another set *B* if every member of *B* is + /// also a member of *A* and *A* contains at least one element that is *not* + /// a member of *B*. (Ignoring the order the elements appear in the sets.) + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: Array = [4, 2, 1] + /// a.isStrictSuperset(of: b) // true + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: `true` if `self` is a strict superset of `other`; otherwise, + /// `false`. + /// + /// - Complexity: Expected to be O(`self.count` + *n*) on average, where *n* + /// is the number of elements in `other`, if `Element` implements + /// high-quality hashing. + @inlinable + public func isStrictSuperset( + of other: S + ) -> Bool where S.Element == Element { + _base.isStrictSuperset(of: other) + } +} + +extension OrderedSet.UnorderedView { + /// Returns a Boolean value that indicates whether the set has no members in + /// common with the given set. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: OrderedSet.UnorderedView = [5, 6] + /// a.isDisjoint(with: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` has no elements in common with `other`; + /// otherwise, `false`. + /// + /// - Complexity: Expected to be O(min(`self.count`, `other.count`)) on + /// average, if `Element` implements high-quality hashing. + @inlinable + public func isDisjoint(with other: Self) -> Bool { + _base.isDisjoint(with: other._base) + } + + // Generalizations + + /// Returns a Boolean value that indicates whether the set has no members in + /// common with the given set. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: Set = [5, 6] + /// a.isDisjoint(with: b) // true + /// + /// - Parameter other: Another set. + /// + /// - Returns: `true` if `self` has no elements in common with `other`; + /// otherwise, `false`. + /// + /// - Complexity: Expected to be O(min(`self.count`, `other.count`)) on + /// average, if `Element` implements high-quality hashing. + @inlinable + public func isDisjoint(with other: Set) -> Bool { + _base.isDisjoint(with: other) + } + + /// Returns a Boolean value that indicates whether the set has no members in + /// common with the given sequence. + /// + /// let a: OrderedSet.UnorderedView = [1, 2, 3, 4] + /// let b: Array = [5, 6] + /// a.isDisjoint(with: b) // true + /// + /// - Parameter other: A finite sequence of elements. + /// + /// - Returns: `true` if `self` has no elements in common with `other`; + /// otherwise, `false`. + /// + /// - Complexity: Expected to be O(*n*) on average, where *n* is the number of + /// elements in `other`, if `Element` implements high-quality hashing. + @inlinable + public func isDisjoint( + with other: S + ) -> Bool where S.Element == Element { + _base.isDisjoint(with: other) + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+UnstableInternals.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+UnstableInternals.swift new file mode 100644 index 0000000000..7896d0cdd6 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet+UnstableInternals.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension OrderedSet { + /// Exposes some private implementation details and low-level unsafe + /// operations, primarily to allow clear box testing. + /// + /// This struct is a private implementation detail, therefore it and its + /// members are not covered by any source compatibility promises -- they + /// may disappear in any new release. + @frozen + public struct _UnstableInternals { + @usableFromInline + internal typealias _Bucket = _HashTable.Bucket + + @usableFromInline + internal var base: OrderedSet + + @inlinable + init(_ base: OrderedSet) { + self.base = base + } + } + + @inlinable + public var __unstable: _UnstableInternals { + @inline(__always) + get { + _UnstableInternals(self) + } + + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + var view = _UnstableInternals(self) + self = OrderedSet() + defer { self = view.base } + yield &view + } + } +} diff --git a/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet.swift b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet.swift new file mode 100644 index 0000000000..5f0fbf70ea --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/OrderedSet/OrderedSet.swift @@ -0,0 +1,482 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +/// An ordered collection of unique elements. +/// +/// Similar to the standard `Set`, ordered sets ensure that each element appears +/// only once in the collection, and they provide efficient tests for +/// membership. However, like `Array` (and unlike `Set`), ordered sets maintain +/// their elements in a particular user-specified order, and they support +/// efficient random-access traversal of their members. +/// +/// `OrderedSet` is a useful alternative to `Set` when the order of elements is +/// important, or when you need to be able to efficiently access elements at +/// various positions within the collection. It can also be used instead of an +/// `Array` when each element needs to be unique, or when you need to be able to +/// quickly determine if a value is a member of the collection. +/// +/// You can create an ordered set with any element type that conforms to the +/// `Hashable` protocol. +/// +/// let buildingMaterials: OrderedSet = ["straw", "sticks", "bricks"] +/// +/// +/// # Equality of Ordered Sets +/// +/// Two ordered sets are considered equal if they contain the same elements, and +/// *in the same order*. This matches the concept of equality of an `Array`, and +/// it is different from the unordered `Set`. +/// +/// let a: OrderedSet = [1, 2, 3, 4] +/// let b: OrderedSet = [4, 3, 2, 1] +/// a == b // false +/// b.sort() // `b` now has value [1, 2, 3, 4] +/// a == b // true +/// +/// # Set Operations +/// +/// `OrderedSet` implements most, but not all, `SetAlgebra` requirements. In +/// particular, it supports the membership test ``contains(_:)`` as well as all +/// high-level set operations such as ``union(_:)-67y2h``, +/// ``intersection(_:)-4o09a`` or ``isSubset(of:)-ptij``. +/// +/// buildingMaterials.contains("glass") // false +/// buildingMaterials.intersection(["brick", "straw"]) // ["straw", "brick"] +/// +/// Operations that return an ordered set usually preserve the ordering of +/// elements in their input. For example, in the case of the `intersection` call +/// above, the ordering of elements in the result is guaranteed to match their +/// order in the first input set, `buildingMaterials`. +/// +/// On the other hand, predicates such as ``isSubset(of:)-ptij`` tend to ignore +/// element ordering: +/// +/// let moreMaterials: OrderedSet = ["bricks", "glass", "sticks", "straw"] +/// buildingMaterials.isSubset(of: moreMaterials) // true +/// +/// `OrderedSet` does not implement `insert(_:)` nor `update(with:)` from +/// `SetAlgebra` -- it provides its own variants for insertion that are more +/// explicit about where in the collection new elements gets inserted: +/// +/// func append(_ item: Element) -> (inserted: Bool, index: Int) +/// func insert(_ item: Element, at index: Int) -> (inserted: Bool, index: Int) +/// func updateOrAppend(_ item: Element) -> Element? +/// func updateOrInsert(_ item: Element, at index: Int) -> (originalMember: Element?, index: Int) +/// func update(_ item: Element, at index: Int) -> Element +/// +/// Additionally,`OrderedSet` has an order-sensitive definition of equality (see +/// above) that is incompatible with `SetAlgebra`'s documented semantic +/// requirements. Accordingly, `OrderedSet` does not (cannot) itself conform to +/// `SetAlgebra`. +/// +/// # Unordered Set View +/// +/// For cases where `SetAlgebra` conformance is desired (such as when passing an +/// ordered set to a function that is generic over that protocol), `OrderedSet` +/// provides an efficient *unordered view* of its elements that conforms to +/// `SetAlgebra`. This view is accessed through the ``unordered`` property, and +/// it implements the same concept of equality as the standard `Set`, ignoring +/// element ordering. +/// +/// var a: OrderedSet = [0, 1, 2, 3] +/// let b: OrderedSet = [3, 2, 1, 0] +/// a == b // false +/// a.unordered == b.unordered // true +/// +/// func frobnicate(_ set: S) { ... } +/// frobnicate(a) // error: `OrderedSet` does not conform to `SetAlgebra` +/// frobnicate(a.unordered) // OK +/// +/// The unordered view is mutable. Insertions into it implicitly append new +/// elements to the end of the collection. +/// +/// buildingMaterials.unordered.insert("glass") // => inserted: true +/// // buildingMaterials is now ["straw", "sticks", "brick", "glass"] +/// +/// Accessing the unordered view is an efficient operation, with constant +/// (minimal) overhead. Direct mutations of the unordered view (such as the +/// insertion above) are executed in place when possible. However, as usual with +/// copy-on-write collections, if you make a copy of the view (such as by +/// extracting its value into a named variable), the resulting values will share +/// the same underlying storage, so mutations of either will incur a copy of the +/// whole set. +/// +/// # Sequence and Collection Operations +/// +/// Ordered sets are random-access collections. Members are assigned integer +/// indices, with the first element always being at index `0`: +/// +/// let buildingMaterials: OrderedSet = ["straw", "sticks", "bricks"] +/// buildingMaterials[1] // "sticks" +/// buildingMaterials.firstIndex(of: "bricks") // 2 +/// +/// for i in 0 ..< buildingMaterials.count { +/// print("Little piggie #\(i) built a house of \(buildingMaterials[i])") +/// } +/// // Little piggie #0 built a house of straw +/// // Little piggie #1 built a house of sticks +/// // Little piggie #2 built a house of bricks +/// +/// Because `OrderedSet` needs to keep its members unique, it cannot conform to +/// the full `MutableCollection` or `RangeReplaceableCollection` protocols. +/// Operations such as `MutableCollection`'s subscript setter or +/// `RangeReplaceableCollection`'s `replaceSubrange` method assume the ability +/// to insert/replace arbitrary elements in the collection, but allowing that +/// could lead to duplicate values. +/// +/// However, `OrderedSet` is able to partially implement these two protocols; +/// namely, is supports mutation operations that merely change the +/// order of elements (such as ``sort()`` or ``swapAt(_:_:)``, or just remove +/// some subset of existing members (such as ``remove(at:)`` or +/// ``removeAll(where:)``). +/// +/// `OrderedSet` also implements ``reserveCapacity(_:)`` from +/// `RangeReplaceableCollection`, to allow for efficient insertion of a known +/// number of elements. (However, unlike `Array` and `Set`, `OrderedSet` does +/// not provide a `capacity` property.) +/// +/// # Accessing The Contents of an Ordered Set as an Array +/// +/// In cases where you need to pass the contents of an ordered set to a function +/// that only takes an array value or (or something that's generic over +/// `RangeReplaceableCollection` or `MutableCollection`), then the best option +/// is usually to directly extract the members of the `OrderedSet` as an `Array` +/// value using its ``elements`` property. `OrderedSet` uses a standard array +/// value for element storage, so extracting the array value has minimal +/// overhead. +/// +/// func pickyFunction(_ items: Array) +/// +/// var set: OrderedSet = [0, 1, 2, 3] +/// pickyFunction(set) // error +/// pickyFunction(set.elements) // OK +/// +/// It is also possible to mutate the set by updating the value of the +/// ``elements`` property. This guarantees that direct mutations happen in place +/// when possible (i.e., without spurious copy-on-write copies). +/// +/// However, the set needs to ensure the uniqueness of its members, so every +/// update to ``elements`` includes a postprocessing step to detect and remove +/// duplicates over the entire array. This can be slower than doing the +/// equivalent updates with direct `OrderedSet` operations, so updating +/// ``elements`` is best used in cases where direct implementations aren't +/// available -- for example, when you need to call a `MutableCollection` +/// algorithm that isn't directly implemented by `OrderedSet` itself. +/// +/// # Performance +/// +/// Like the standard `Set` type, the performance of hashing operations in +/// `OrderedSet` is highly sensitive to the quality of hashing implemented by +/// the `Element` type. Failing to correctly implement hashing can easily lead +/// to unacceptable performance, with the severity of the effect increasing with +/// the size of the hash table. +/// +/// In particular, if a certain set of elements all produce the same hash value, +/// then hash table lookups regress to searching an element in an unsorted +/// array, i.e., a linear operation. To ensure hashed collection types exhibit +/// their target performance, it is important to ensure that such collisions +/// cannot be induced merely by adding a particular list of members to the set. +/// +/// The easiest way to achieve this is to make sure `Element` implements hashing +/// following `Hashable`'s documented best practices. The conformance must +/// implement the `hash(into:)` requirement, and every bit of information that +/// is compared in `==` needs to be combined into the supplied `Hasher` value. +/// When used correctly, `Hasher` produces high-quality, randomly seeded hash +/// values that prevent repeatable hash collisions. +/// +/// When `Element` implements `Hashable` correctly, testing for membership in an +/// ordered set is expected to take O(1) equality checks on average. Hash +/// collisions can still occur organically, so the worst-case lookup performance +/// is technically still O(*n*) (where *n* is the size of the set); however, +/// long lookup chains are unlikely to occur in practice. +/// +/// # Implementation Details +/// +/// An `OrderedSet` stores its members in a regular `Array` value (exposed by +/// the `elements` property). It also maintains a standalone hash table +/// containing array indices alongside the array; this is used to implement fast +/// membership tests. The size of the array is limited by the capacity of the +/// corresponding hash table, so indices stored inside the hash table can be +/// encoded into fewer bits than a standard `Int` value, leading to a storage +/// representation that can often be more compact than that of `Set` itself. +/// +/// Inserting or removing a single member (or a range of members) needs to +/// perform the corresponding operation in the storage array, in addition to +/// renumbering any subsequent members in the hash table. Therefore, these +/// operations are expected to have performance characteristics similar to an +/// `Array`: inserting or removing an element to the end of an ordered set is +/// expected to execute in O(1) operations, while they are expected to take +/// linear time at the front (or in the middle) of the set. (Note that this is +/// different to the standard `Set`, where insertions and removals are expected +/// to take amortized O(1) time.) +@frozen +public struct OrderedSet where Element: Hashable +{ + @usableFromInline + internal typealias _Bucket = _HashTable.Bucket + + @usableFromInline + internal var __storage: _HashTable.Storage? + + @usableFromInline + internal var _elements: ContiguousArray + + @inlinable + internal init( + _uniqueElements: ContiguousArray, + _ table: _HashTable? + ) { + self.__storage = table?._storage + self._elements = _uniqueElements + } + + @inlinable + @inline(__always) + internal var _table: _HashTable? { + get { __storage.map { _HashTable($0) } } + set { __storage = newValue?._storage } + } +} + +extension OrderedSet { + /// A view of the members of this set, as a regular array value. + /// + /// It is possible to mutate the set by updating the value of this property. + /// This guarantees that direct mutations happen in place when possible (i.e., + /// without spurious copy-on-write copies). + /// + /// However, the set needs to ensure the uniqueness of its members, so every + /// update to `elements` includes a postprocessing step to detect and remove + /// duplicates over the entire array. This can be slower than doing the + /// equivalent updates with direct `OrderedSet` operations, so updating + /// `elements` is best used in cases where direct implementations aren't + /// available -- for example, when you need to call a `MutableCollection` + /// algorithm that isn't directly implemented by `OrderedSet` itself. + /// + /// - Complexity: O(1) for the getter. Mutating this property has an expected + /// complexity of O(`count`), if `Element` implements high-quality hashing. + @inlinable + public var elements: [Element] { + get { + Array(_elements) + } + set { + self = .init(newValue) + } + @inline(__always) // https://github.com/apple/swift-collections/issues/164 + _modify { + var members = Array(_elements) + _elements = [] + defer { self = .init(members) } + yield &members + } + } +} + +extension OrderedSet { + /// The maximum number of elements this instance can store before it needs + /// to resize its hash table. + @inlinable + internal var _capacity: Int { + _table?.capacity ?? _HashTable.maximumUnhashedCount + } + + @inlinable + internal var _minimumCapacity: Int { + if _scale == _reservedScale { return 0 } + return _HashTable.minimumCapacity(forScale: _scale) + } + + @inlinable + internal var _scale: Int { + _table?.scale ?? 0 + } + + @inlinable + internal var _reservedScale: Int { + _table?.reservedScale ?? 0 + } + + @inlinable + internal var _bias: Int { + _table?.bias ?? 0 + } +} + +extension OrderedSet { + @inlinable + internal mutating func _regenerateHashTable(scale: Int, reservedScale: Int) { + assert(_HashTable.maximumCapacity(forScale: scale) >= _elements.count) + assert(reservedScale == 0 || reservedScale >= _HashTable.minimumScale) + _table = _HashTable.create( + uncheckedUniqueElements: _elements, + scale: Swift.max(scale, reservedScale), + reservedScale: reservedScale) + } + + @inlinable + internal mutating func _regenerateHashTable() { + let reservedScale = _reservedScale + guard + _elements.count > _HashTable.maximumUnhashedCount || reservedScale != 0 + else { + // We have too few elements; disable hashing. + _table = nil + return + } + let scale = _HashTable.scale(forCapacity: _elements.count) + _regenerateHashTable(scale: scale, reservedScale: reservedScale) + } + + @inlinable + internal mutating func _regenerateExistingHashTable() { + assert(_capacity >= _elements.count) + guard _table != nil else { + return + } + _ensureUnique() + _table!.update { hashTable in + hashTable.clear() + hashTable.fill(uncheckedUniqueElements: _elements) + } + } +} + +extension OrderedSet { + @inlinable + @inline(__always) + internal mutating func _isUnique() -> Bool { + isKnownUniquelyReferenced(&__storage) + } + + @inlinable + internal mutating func _ensureUnique() { + if __storage == nil { return } + if isKnownUniquelyReferenced(&__storage) { return } + _table = _table!.copy() + } +} + +extension OrderedSet { + @inlinable + internal func _find(_ item: Element) -> (index: Int?, bucket: _Bucket) { + _find_inlined(item) + } + + @inlinable + @inline(__always) + internal func _find_inlined(_ item: Element) -> (index: Int?, bucket: _Bucket) { + _elements.withUnsafeBufferPointer { elements in + guard let table = _table else { + return (elements.firstIndex(of: item), _Bucket(offset: 0)) + } + return table.read { hashTable in + hashTable._find(item, in: elements) + } + } + } + + @inlinable + internal func _bucket(for index: Int) -> _Bucket { + guard let table = _table else { return _Bucket(offset: 0) } + return table.read { hashTable in + var it = hashTable.bucketIterator(for: _elements[index]) + it.advance(until: index) + precondition(it.isOccupied, "Corrupt hash table") + return it.currentBucket + } + } + + /// Returns the index of the given element in the set, or `nil` if the element + /// is not a member of the set. + /// + /// `OrderedSet` members are always unique, so the first index of an element + /// is always the same as its last index. + /// + /// - Complexity: This operation is expected to perform O(1) comparisons on + /// average, provided that `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public func firstIndex(of element: Element) -> Int? { + _find(element).index + } + + /// Returns the index of the given element in the set, or `nil` if the element + /// is not a member of the set. + /// + /// `OrderedSet` members are always unique, so the first index of an element + /// is always the same as its last index. + /// + /// - Complexity: This operation is expected to perform O(1) comparisons on + /// average, provided that `Element` implements high-quality hashing. + @inlinable + @inline(__always) + public func lastIndex(of element: Element) -> Int? { + _find(element).index + } +} + +extension OrderedSet { + @inlinable + @inline(never) + internal __consuming func _extractSubset( + using bitset: _UnsafeBitset, + extraCapacity: Int = 0 + ) -> Self { + assert(bitset.count == 0 || bitset.max()! <= count) + if bitset.count == 0 { return Self(minimumCapacity: extraCapacity) } + if bitset.count == self.count { + if extraCapacity <= self._capacity - self.count { + return self + } + var copy = self + copy.reserveCapacity(count + extraCapacity) + return copy + } + var result = Self(minimumCapacity: bitset.count + extraCapacity) + for offset in bitset { + result._appendNew(_elements[offset]) + } + assert(result.count == bitset.count) + return result + } +} + +extension OrderedSet { + @inlinable + @discardableResult + internal mutating func _removeExistingMember( + at index: Int, + in bucket: _Bucket + ) -> Element { + guard _elements.count - 1 >= _minimumCapacity else { + let old = _elements.remove(at: index) + _regenerateHashTable() + return old + } + guard _table != nil else { + return _elements.remove(at: index) + } + + defer { _checkInvariants() } + _ensureUnique() + _table!.update { hashTable in + // Delete the entry for the removed member. + hashTable.delete( + bucket: bucket, + hashValueGenerator: { offset, seed in + _elements[offset]._rawHashValue(seed: seed) + }) + hashTable.adjustContents(preparingForRemovalOf: index, in: _elements) + } + return _elements.remove(at: index) + } +} diff --git a/Sources/PluginCore/OrderedCollections/Utilities/RandomAccessCollection+Offsets.swift b/Sources/PluginCore/OrderedCollections/Utilities/RandomAccessCollection+Offsets.swift new file mode 100644 index 0000000000..54a56fa86e --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/Utilities/RandomAccessCollection+Offsets.swift @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension RandomAccessCollection { + @inlinable + @inline(__always) + internal func _index(at offset: Int) -> Index { + index(startIndex, offsetBy: offset) + } + + @inlinable + @inline(__always) + internal func _offset(of index: Index) -> Int { + distance(from: startIndex, to: index) + } + + @inlinable + @inline(__always) + internal subscript(_offset offset: Int) -> Element { + self[_index(at: offset)] + } +} diff --git a/Sources/PluginCore/OrderedCollections/Utilities/_UnsafeBitset.swift b/Sources/PluginCore/OrderedCollections/Utilities/_UnsafeBitset.swift new file mode 100644 index 0000000000..aa10c68433 --- /dev/null +++ b/Sources/PluginCore/OrderedCollections/Utilities/_UnsafeBitset.swift @@ -0,0 +1,395 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +/// A simple bitmap of a fixed number of bits, implementing a sorted set of +/// small nonnegative `Int` values. +/// +/// Because `_UnsafeBitset` implements a flat bit vector, it isn't suitable for +/// holding arbitrarily large integers. The maximal element a bitset can store +/// is fixed at its initialization. +@usableFromInline +@frozen +internal struct _UnsafeBitset { + @usableFromInline + internal let _words: UnsafeMutableBufferPointer + + @usableFromInline + internal var _count: Int + + @inlinable + @inline(__always) + internal init(words: UnsafeMutableBufferPointer, count: Int) { + self._words = words + self._count = count + } + + @inlinable + @inline(__always) + internal init(words: UnsafeMutablePointer, wordCount: Int, count: Int) { + self._words = UnsafeMutableBufferPointer(start: words, count: wordCount) + self._count = count + } + + @inlinable + @inline(__always) + internal var count: Int { + _count + } +} + +extension _UnsafeBitset { + @usableFromInline + internal var _actualCount: Int { + return _words.reduce(0) { $0 + $1.count } + } +} + +extension _UnsafeBitset { + @inlinable + @inline(__always) + static func withTemporaryBitset( + capacity: Int, + run body: (inout _UnsafeBitset) throws -> R + ) rethrows -> R { + var result: R? + try _withTemporaryBitset(capacity: capacity) { bitset in + result = try body(&bitset) + } + return result! + } + + @usableFromInline + @inline(never) + static func _withTemporaryBitset( + capacity: Int, + run body: (inout _UnsafeBitset) throws -> Void + ) rethrows { + let wordCount = _UnsafeBitset.wordCount(forCapacity: capacity) +#if compiler(>=5.6) + return try withUnsafeTemporaryAllocation( + of: Word.self, capacity: wordCount + ) { words in + words.initialize(repeating: .empty) + var bitset = Self(words: words, count: 0) + return try body(&bitset) + } +#else + if wordCount <= 2 { + var buffer: (Word, Word) = (.empty, .empty) + return try withUnsafeMutablePointer(to: &buffer) { p in + // Homogeneous tuples are layout-compatible with their component type. + let words = UnsafeMutableRawPointer(p).assumingMemoryBound(to: Word.self) + var bitset = _UnsafeBitset(words: words, wordCount: wordCount, count: 0) + return try body(&bitset) + } + } + let words = UnsafeMutableBufferPointer.allocate(capacity: wordCount) + words.initialize(repeating: .empty) + defer { words.deallocate() } + var bitset = _UnsafeBitset(words: words, count: 0) + return try body(&bitset) +#endif + } +} + +extension _UnsafeBitset { + @inline(__always) + internal static func word(for element: Int) -> Int { + assert(element >= 0) + // Note: We perform on UInts to get faster unsigned math (shifts). + let element = UInt(bitPattern: element) + let capacity = UInt(bitPattern: Word.capacity) + return Int(bitPattern: element / capacity) + } + + @inline(__always) + internal static func bit(for element: Int) -> Int { + assert(element >= 0) + // Note: We perform on UInts to get faster unsigned math (masking). + let element = UInt(bitPattern: element) + let capacity = UInt(bitPattern: Word.capacity) + return Int(bitPattern: element % capacity) + } + + @inline(__always) + internal static func split(_ element: Int) -> (word: Int, bit: Int) { + return (word(for: element), bit(for: element)) + } + + @inline(__always) + internal static func join(word: Int, bit: Int) -> Int { + assert(bit >= 0 && bit < Word.capacity) + return word &* Word.capacity &+ bit + } +} + +extension _UnsafeBitset { + @usableFromInline + @_effects(readnone) + @inline(__always) + internal static func wordCount(forCapacity capacity: Int) -> Int { + return word(for: capacity &+ Word.capacity &- 1) + } + + internal var capacity: Int { + @inline(__always) + get { + return _words.count &* Word.capacity + } + } + + @inline(__always) + internal func isValid(_ element: Int) -> Bool { + return element >= 0 && element < capacity + } + + @inline(__always) + internal func contains(_ element: Int) -> Bool { + assert(isValid(element)) + let (word, bit) = _UnsafeBitset.split(element) + return _words[word].contains(bit) + } + + @usableFromInline + @_effects(releasenone) + @discardableResult + internal mutating func insert(_ element: Int) -> Bool { + assert(isValid(element)) + let (word, bit) = _UnsafeBitset.split(element) + let inserted = _words[word].insert(bit) + if inserted { _count += 1 } + return inserted + } + + @usableFromInline + @_effects(releasenone) + @discardableResult + internal mutating func remove(_ element: Int) -> Bool { + assert(isValid(element)) + let (word, bit) = _UnsafeBitset.split(element) + let removed = _words[word].remove(bit) + if removed { _count -= 1 } + return removed + } + + @usableFromInline + @_effects(releasenone) + internal mutating func clear() { + guard _words.count > 0 else { return } + _words.baseAddress!.assign(repeating: .empty, count: _words.count) + _count = 0 + } + + @usableFromInline + @_effects(releasenone) + internal mutating func insertAll(upTo max: Int) { + assert(max <= capacity) + guard max > 0 else { return } + let (w, b) = _UnsafeBitset.split(max) + for i in 0 ..< w { + _count += Word.capacity - _words[i].count + _words[i] = .allBits + } + if b > 0 { + _count += _words[w].insert(bitsBelow: b) + } + } + + @usableFromInline + @_effects(releasenone) + internal mutating func removeAll(upTo max: Int) { + assert(max <= capacity) + guard max > 0 else { return } + let (w, b) = _UnsafeBitset.split(max) + for i in 0 ..< w { + _count -= _words[i].count + _words[i] = .empty + } + if b > 0 { + _count -= _words[w].remove(bitsBelow: b) + } + } +} + +extension _UnsafeBitset: Sequence { + @usableFromInline + internal typealias Element = Int + + @inlinable + @inline(__always) + internal var underestimatedCount: Int { + return count + } + + @inlinable + @inline(__always) + func makeIterator() -> Iterator { + return Iterator(self) + } + + @usableFromInline + @frozen + internal struct Iterator: IteratorProtocol { + @usableFromInline + internal let bitset: _UnsafeBitset + + @usableFromInline + internal var index: Int + + @usableFromInline + internal var word: Word + + @inlinable + internal init(_ bitset: _UnsafeBitset) { + self.bitset = bitset + self.index = 0 + self.word = bitset._words.count > 0 ? bitset._words[0] : .empty + } + + @usableFromInline + @_effects(releasenone) + internal mutating func next() -> Int? { + if let bit = word.next() { + return _UnsafeBitset.join(word: index, bit: bit) + } + while (index + 1) < bitset._words.count { + index += 1 + word = bitset._words[index] + if let bit = word.next() { + return _UnsafeBitset.join(word: index, bit: bit) + } + } + return nil + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +extension _UnsafeBitset { + @usableFromInline + @frozen + internal struct Word { + @usableFromInline + internal var value: UInt + + @inlinable + @inline(__always) + internal init(_ value: UInt) { + self.value = value + } + } +} + +extension _UnsafeBitset.Word { + @inlinable + @inline(__always) + internal static var capacity: Int { + return UInt.bitWidth + } + + @inlinable + @inline(__always) + internal var count: Int { + value.nonzeroBitCount + } + + @inlinable + @inline(__always) + internal var isEmpty: Bool { + value == 0 + } + + @inlinable + @inline(__always) + internal func contains(_ bit: Int) -> Bool { + assert(bit >= 0 && bit < UInt.bitWidth) + return value & (1 &<< bit) != 0 + } + + @inlinable + @inline(__always) + @discardableResult + internal mutating func insert(_ bit: Int) -> Bool { + assert(bit >= 0 && bit < UInt.bitWidth) + let mask: UInt = 1 &<< bit + let inserted = value & mask == 0 + value |= mask + return inserted + } + + @inlinable + @inline(__always) + @discardableResult + internal mutating func remove(_ bit: Int) -> Bool { + assert(bit >= 0 && bit < UInt.bitWidth) + let mask: UInt = 1 &<< bit + let removed = value & mask != 0 + value &= ~mask + return removed + } +} + +extension _UnsafeBitset.Word { + @inlinable + @inline(__always) + internal mutating func insert(bitsBelow bit: Int) -> Int { + assert(bit >= 0 && bit < Self.capacity) + let mask: UInt = (1 as UInt &<< bit) &- 1 + let inserted = bit - (value & mask).nonzeroBitCount + value |= mask + return inserted + } + + @inlinable + @inline(__always) + internal mutating func remove(bitsBelow bit: Int) -> Int { + assert(bit >= 0 && bit < Self.capacity) + let mask = UInt.max &<< bit + let removed = (value & ~mask).nonzeroBitCount + value &= mask + return removed + } +} + +extension _UnsafeBitset.Word { + @inlinable + @inline(__always) + internal static var empty: Self { + Self(0) + } + + @inlinable + @inline(__always) + internal static var allBits: Self { + Self(UInt.max) + } +} + +// Word implements Sequence by using a copy of itself as its Iterator. +// Iteration with `next()` destroys the word's value; however, this won't cause +// problems in normal use, because `next()` is usually called on a separate +// iterator, not the original word. +extension _UnsafeBitset.Word: Sequence, IteratorProtocol { + @inlinable + internal var underestimatedCount: Int { + count + } + + /// Return the index of the lowest set bit in this word, + /// and also destructively clear it. + @inlinable + internal mutating func next() -> Int? { + guard value != 0 else { return nil } + let bit = value.trailingZeroBitCount + value &= value &- 1 // Clear lowest nonzero bit. + return bit + } +} diff --git a/Sources/PluginCore/Variables/Property/AliasedPropertyVariable.swift b/Sources/PluginCore/Variables/Property/AliasedPropertyVariable.swift index 99379b7966..30384ce6d7 100644 --- a/Sources/PluginCore/Variables/Property/AliasedPropertyVariable.swift +++ b/Sources/PluginCore/Variables/Property/AliasedPropertyVariable.swift @@ -1,4 +1,3 @@ -import OrderedCollections import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros diff --git a/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode+CodingData.swift b/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode+CodingData.swift index 344907b8ee..bff34ebc51 100644 --- a/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode+CodingData.swift +++ b/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode+CodingData.swift @@ -1,4 +1,3 @@ -import OrderedCollections import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros diff --git a/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift b/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift index f22f224836..12c574cdea 100644 --- a/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift +++ b/Sources/PluginCore/Variables/Property/Tree/PropertyVariableTreeNode.swift @@ -1,4 +1,3 @@ -import OrderedCollections import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros diff --git a/Sources/PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift b/Sources/PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift index 4dca4dee6d..4ecb4c1abc 100644 --- a/Sources/PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift +++ b/Sources/PluginCore/Variables/Type/Data/CodingKeysMap/CodingKeysMap.swift @@ -1,4 +1,3 @@ -import OrderedCollections import SwiftSyntax import SwiftSyntaxMacros diff --git a/Utils/spec.rb b/Utils/spec.rb index a42f5d8536..065a7a06fd 100644 --- a/Utils/spec.rb +++ b/Utils/spec.rb @@ -1,4 +1,5 @@ require 'json' +require 'ostruct' module MetaCodable module Spec diff --git a/Vendor/swift-collections b/Vendor/swift-collections new file mode 160000 index 0000000000..d029d9d39c --- /dev/null +++ b/Vendor/swift-collections @@ -0,0 +1 @@ +Subproject commit d029d9d39c87bed85b1c50adee7c41795261a192 From 63f373e486ab047ce1e94776b416944cc81e7714 Mon Sep 17 00:00:00 2001 From: elppaaa Date: Tue, 16 Sep 2025 15:27:36 +0900 Subject: [PATCH 2/2] =?UTF-8?q?OrderedCollections=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/PluginCore/Variables/Type/MemberGroup.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PluginCore/Variables/Type/MemberGroup.swift b/Sources/PluginCore/Variables/Type/MemberGroup.swift index fe3f2c6236..c886412cc1 100644 --- a/Sources/PluginCore/Variables/Type/MemberGroup.swift +++ b/Sources/PluginCore/Variables/Type/MemberGroup.swift @@ -1,4 +1,4 @@ -import OrderedCollections + import SwiftSyntax import SwiftSyntaxMacros