//===----------------------------------------------------------------------===// // // This source file is part of the SwiftCertificates open source project // // Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information // See CONTRIBUTORS.txt for the list of SwiftCertificates project authors // // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// //Taken from: https://github.com/apple/swift-certificates /// ``_TinyArray`` is a ``RandomAccessCollection`` optimised to store zero or one ``Element``. /// It supports arbitrary many elements but if only up to one ``Element`` is stored it does **not** allocate separate storage on the heap /// and instead stores the ``Element`` inline. public struct _TinyArray { @usableFromInline enum Storage { case one(Element) case arbitrary([Element]) } @usableFromInline var storage: Storage } // MARK: - TinyArray "public" interface extension _TinyArray: Equatable where Element: Equatable {} extension _TinyArray: Hashable where Element: Hashable {} extension _TinyArray: Sendable where Element: Sendable {} extension _TinyArray: ExpressibleByArrayLiteral { @inlinable public init(arrayLiteral elements: Element...) { switch elements.count { case 0: self = .init() case 1: self = .init(CollectionOfOne(elements[0])) default: self = .init(elements) } } } extension _TinyArray: RandomAccessCollection { public typealias Element = Element public typealias Index = Int @inlinable public subscript(position: Int) -> Element { get { self.storage[position] } set { self.storage[position] = newValue } } @inlinable public var startIndex: Int { self.storage.startIndex } @inlinable public var endIndex: Int { self.storage.endIndex } } extension _TinyArray { @inlinable public init(_ elements: some Sequence) { self.storage = .init(elements) } @inlinable public init(_ elements: some Sequence>) throws { self.storage = try .init(elements) } @inlinable public init() { self.storage = .init() } @inlinable public mutating func append(_ newElement: Element) { self.storage.append(newElement) } @inlinable public mutating func append(contentsOf newElements: some Sequence) { self.storage.append(contentsOf: newElements) } @discardableResult @inlinable public mutating func remove(at index: Int) -> Element { self.storage.remove(at: index) } @inlinable public mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows { try self.storage.removeAll(where: shouldBeRemoved) } @inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows { try self.storage.sort(by: areInIncreasingOrder) } @inlinable public func flatMap(_ transform: (Element) throws -> SegmentOfResult) rethrows -> _TinyArray where SegmentOfResult : Sequence { try self.storage.flatMap(transform) } @inlinable public func flatMap(_ transform: (Element) throws -> _TinyArray) rethrows -> _TinyArray where Element == _TinyArray { try self.storage.flatMap(transform) } @inlinable public func flatMap() -> _TinyArray where Element == _TinyArray { self.storage.flatMap() } } // MARK: - TinyArray.Storage "private" implementation extension _TinyArray.Storage: Equatable where Element: Equatable { @inlinable static func == (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { case (.one(let lhs), .one(let rhs)): return lhs == rhs case (.arbitrary(let lhs), .arbitrary(let rhs)): // we don't use lhs.elementsEqual(rhs) so we can hit the fast path from Array // if both arrays share the same underlying storage: https://github.com/apple/swift/blob/b42019005988b2d13398025883e285a81d323efa/stdlib/public/core/Array.swift#L1775 return lhs == rhs case (.one(let element), .arbitrary(let array)), (.arbitrary(let array), .one(let element)): guard array.count == 1 else { return false } return element == array[0] } } } extension _TinyArray.Storage: Hashable where Element: Hashable { @inlinable func hash(into hasher: inout Hasher) { // same strategy as Array: https://github.com/apple/swift/blob/b42019005988b2d13398025883e285a81d323efa/stdlib/public/core/Array.swift#L1801 hasher.combine(count) for element in self { hasher.combine(element) } } } extension _TinyArray.Storage: Sendable where Element: Sendable {} extension _TinyArray.Storage: RandomAccessCollection { @inlinable subscript(position: Int) -> Element { get { switch self { case .one(let element): guard position == 0 else { fatalError("index \(position) out of bounds") } return element case .arbitrary(let elements): return elements[position] } } set { switch self { case .one: guard position == 0 else { fatalError("index \(position) out of bounds") } self = .one(newValue) case .arbitrary(var elements): elements[position] = newValue self = .arbitrary(elements) } } } @inlinable var startIndex: Int { 0 } @inlinable var endIndex: Int { switch self { case .one: return 1 case .arbitrary(let elements): return elements.endIndex } } } extension _TinyArray.Storage { @inlinable init(_ elements: some Sequence) { self = .arbitrary([]) self.append(contentsOf: elements) } @inlinable init(_ newElements: some Sequence>) throws { var iterator = newElements.makeIterator() guard let firstElement = try iterator.next()?.get() else { self = .arbitrary([]) return } guard let secondElement = try iterator.next()?.get() else { // newElements just contains a single element // and we hit the fast path self = .one(firstElement) return } var elements: [Element] = [] elements.reserveCapacity(newElements.underestimatedCount) elements.append(firstElement) elements.append(secondElement) while let nextElement = try iterator.next()?.get() { elements.append(nextElement) } self = .arbitrary(elements) } @inlinable init() { self = .arbitrary([]) } @inlinable mutating func append(_ newElement: Element) { self.append(contentsOf: CollectionOfOne(newElement)) } @inlinable mutating func append(contentsOf newElements: some Sequence) { switch self { case .one(let firstElement): var iterator = newElements.makeIterator() guard let secondElement = iterator.next() else { // newElements is empty, nothing to do return } var elements: [Element] = [] elements.reserveCapacity(1 + newElements.underestimatedCount) elements.append(firstElement) elements.append(secondElement) elements.appendRemainingElements(from: &iterator) self = .arbitrary(elements) case .arbitrary(var elements): if elements.isEmpty { // if `self` is currently empty and `newElements` just contains a single // element, we skip allocating an array and set `self` to `.one(firstElement)` var iterator = newElements.makeIterator() guard let firstElement = iterator.next() else { // newElements is empty, nothing to do return } guard let secondElement = iterator.next() else { // newElements just contains a single element // and we hit the fast path self = .one(firstElement) return } elements.reserveCapacity(elements.count + newElements.underestimatedCount) elements.append(firstElement) elements.append(secondElement) elements.appendRemainingElements(from: &iterator) self = .arbitrary(elements) } else { elements.append(contentsOf: newElements) self = .arbitrary(elements) } } } @discardableResult @inlinable mutating func remove(at index: Int) -> Element { switch self { case .one(let oldElement): guard index == 0 else { fatalError("index \(index) out of bounds") } self = .arbitrary([]) return oldElement case .arbitrary(var elements): defer { self = .arbitrary(elements) } return elements.remove(at: index) } } @inlinable mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows { switch self { case .one(let oldElement): if try shouldBeRemoved(oldElement) { self = .arbitrary([]) } case .arbitrary(var elements): defer { self = .arbitrary(elements) } return try elements.removeAll(where: shouldBeRemoved) } } @inlinable mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows { switch self { case .one: // a collection of just one element is always sorted, nothing to do break case .arbitrary(var elements): defer { self = .arbitrary(elements) } try elements.sort(by: areInIncreasingOrder) } } @inlinable func flatMap(_ transform: (Element) throws -> SegmentOfResult) rethrows -> _TinyArray where SegmentOfResult : Sequence { switch self { case .one(let element): let sequence:SegmentOfResult = try transform(element) let result:_TinyArray = _TinyArray(sequence) return result case .arbitrary(let elements): let seq:Array = try elements.flatMap({ try transform($0) }) let result:_TinyArray = _TinyArray(seq) return result } } @inlinable func flatMap(_ transform: (Element) throws -> _TinyArray) rethrows -> _TinyArray where Element == _TinyArray { switch self { case .one(let element): let test:_TinyArray = element let sequence:_TinyArray = try transform(element) return sequence case .arbitrary(let elements): let seq:Array = try elements.flatMap({ try transform($0) }) let result:_TinyArray = _TinyArray(seq) return result } } @inlinable func flatMap() -> _TinyArray where Element == _TinyArray { switch self { case .one(let element): return element case .arbitrary(let elements): let seq:Array = elements.flatMap({ $0 }) let result:_TinyArray = _TinyArray(seq) return result } } } extension Array { @inlinable func flatMap() -> Array where Element == Array { if (self.count == 1) { return self.first! } else { let arr = self.flatMap { $0 } return arr } } @inlinable mutating func appendRemainingElements(from iterator: inout some IteratorProtocol) { while let nextElement = iterator.next() { append(nextElement) } } @inlinable func flatMapToTiny() -> _TinyArray where Element == _TinyArray { if (self.count == 1) { return self.first! } else { let arr = self.flatMap({ $0 }) return _TinyArray(arr) } } }