diff --git a/Sources/OpenSwiftUICore/Data/Util/Cache3.swift b/Sources/OpenSwiftUICore/Data/Util/Cache3.swift deleted file mode 100644 index ec7e78096..000000000 --- a/Sources/OpenSwiftUICore/Data/Util/Cache3.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// Cache3.swift -// OpenSwiftUICore -// -// Audited for iOS 18.0 -// Status: Complete - -/// A simple fixed-size cache that stores up to three key-value pairs. -/// -/// Cache3 provides a lightweight, efficient cache implementation with LRU (Least Recently Used) -/// eviction behavior. When a new item is added to a full cache, the oldest item is evicted. -/// -/// Example usage: -/// -/// var cache = Cache3() -/// cache.put("one", value: 1) -/// cache.put("two", value: 2) -/// let value = cache.get("three") { 3 } // Creates and caches value 3 -/// -package struct Cache3 where Key: Equatable { - /// Internal tuple-based storage for the cached items. - /// The first element represents the most recently used item. - var store: ((key: Key, value: Value)?, (key: Key, value: Value)?, (key: Key, value: Value)?) - - /// Creates a new empty cache. - package init() { - self.store = (nil, nil, nil) - } - - /// Looks up a value in the cache by key without changing cache order. - /// - /// - Parameter key: The key to look up. - /// - Returns: The value associated with the key, or `nil` if the key is not in the cache. - @inline(__always) - package func find(_ key: Key) -> Value? { - if let item = store.0, item.key == key { - return item.value - } - if let item = store.1, item.key == key { - return item.value - } - if let item = store.2, item.key == key { - return item.value - } - return nil - } - - /// Inserts a new value into the cache with the specified key. - /// - /// This method adds a new key-value pair to the cache, making it the most recently used item. - /// If the cache already has 3 items, the least recently used item is evicted. - /// - /// - Parameters: - /// - key: The key to associate with the value. - /// - value: The value to cache. - @inline(__always) - package mutating func put(_ key: Key, value: Value) { - store = ((key, value), store.0, store.1) - } - - /// Retrieves a value from the cache by key, creating it if not present. - /// - /// This method first checks if the key exists in the cache. If found, it returns the - /// associated value. If not found, it calls the provided closure to create a new value, - /// caches it, and returns the newly created value. - /// - /// - Parameters: - /// - key: The key to look up. - /// - makeValue: A closure that creates a new value if the key is not found. - /// - Returns: The value associated with the key, either retrieved from cache or newly created. - @inline(__always) - package mutating func get(_ key: Key, makeValue: () -> Value) -> Value { - guard let value = find(key) else { - let value = makeValue() - put(key, value: value) - return value - } - return value - } -} diff --git a/Sources/OpenSwiftUICore/Runtime/TypeConformance.swift b/Sources/OpenSwiftUICore/Runtime/TypeConformance.swift index 05b9d5331..3a0661171 100644 --- a/Sources/OpenSwiftUICore/Runtime/TypeConformance.swift +++ b/Sources/OpenSwiftUICore/Runtime/TypeConformance.swift @@ -49,12 +49,8 @@ package struct TypeConformance

where P: ProtocolDescriptor { } } -package func conformsToProtocol(_ type: any Any.Type, _ desc: UnsafeRawPointer) -> Bool { - swiftConformsToProtocol(type, desc) != nil -} - @_silgen_name("swift_conformsToProtocol") -private func swiftConformsToProtocol( +func swiftConformsToProtocol( _ type: Any.Type, _ protocolDescriptor: UnsafeRawPointer ) -> UnsafeRawPointer? diff --git a/Sources/OpenSwiftUICore/Data/Util/AnyHashable2.swift b/Sources/OpenSwiftUICore/Util/Data/AnyHashable2.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/AnyHashable2.swift rename to Sources/OpenSwiftUICore/Util/Data/AnyHashable2.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/BitVector.swift b/Sources/OpenSwiftUICore/Util/Data/BitVector.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/BitVector.swift rename to Sources/OpenSwiftUICore/Util/Data/BitVector.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/BitVector64.swift b/Sources/OpenSwiftUICore/Util/Data/BitVector64.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/BitVector64.swift rename to Sources/OpenSwiftUICore/Util/Data/BitVector64.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/BloomFilter.swift b/Sources/OpenSwiftUICore/Util/Data/BloomFilter.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/BloomFilter.swift rename to Sources/OpenSwiftUICore/Util/Data/BloomFilter.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/Box.swift b/Sources/OpenSwiftUICore/Util/Data/Box.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/Box.swift rename to Sources/OpenSwiftUICore/Util/Data/Box.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/ConcatenatedCollection.swift b/Sources/OpenSwiftUICore/Util/Data/ConcatenatedCollection.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/ConcatenatedCollection.swift rename to Sources/OpenSwiftUICore/Util/Data/ConcatenatedCollection.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/InlineArray.swift b/Sources/OpenSwiftUICore/Util/Data/InlineArray.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/InlineArray.swift rename to Sources/OpenSwiftUICore/Util/Data/InlineArray.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/ObjectCache.swift b/Sources/OpenSwiftUICore/Util/Data/ObjectCache.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/ObjectCache.swift rename to Sources/OpenSwiftUICore/Util/Data/ObjectCache.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/Stack.swift b/Sources/OpenSwiftUICore/Util/Data/Stack.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/Stack.swift rename to Sources/OpenSwiftUICore/Util/Data/Stack.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/UniqueID.swift b/Sources/OpenSwiftUICore/Util/Data/UniqueID.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/UniqueID.swift rename to Sources/OpenSwiftUICore/Util/Data/UniqueID.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/VersionSeed.swift b/Sources/OpenSwiftUICore/Util/Data/VersionSeed.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/VersionSeed.swift rename to Sources/OpenSwiftUICore/Util/Data/VersionSeed.swift diff --git a/Sources/OpenSwiftUICore/Data/Util/VersionSeedTracker.swift b/Sources/OpenSwiftUICore/Util/Data/VersionSeedTracker.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/VersionSeedTracker.swift rename to Sources/OpenSwiftUICore/Util/Data/VersionSeedTracker.swift diff --git a/Sources/OpenSwiftUICore/Extension/CGAffineTransform+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/CGAffineTransform+Extension.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/CGAffineTransform+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/CGAffineTransform+Extension.swift diff --git a/Sources/OpenSwiftUICore/Extension/CGPoint+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/CGPoint+Extension.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/CGPoint+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/CGPoint+Extension.swift diff --git a/Sources/OpenSwiftUICore/Extension/CGPoint+Math.swift b/Sources/OpenSwiftUICore/Util/Extension/CGPoint+Math.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/CGPoint+Math.swift rename to Sources/OpenSwiftUICore/Util/Extension/CGPoint+Math.swift diff --git a/Sources/OpenSwiftUICore/Extension/CGRect+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/CGRect+Extension.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/CGRect+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/CGRect+Extension.swift diff --git a/Sources/OpenSwiftUICore/Extension/CGSize+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/CGSize+Extension.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/CGSize+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/CGSize+Extension.swift diff --git a/Sources/OpenSwiftUICore/Extension/CGSize+Math.swift b/Sources/OpenSwiftUICore/Util/Extension/CGSize+Math.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/CGSize+Math.swift rename to Sources/OpenSwiftUICore/Util/Extension/CGSize+Math.swift diff --git a/Sources/OpenSwiftUICore/Extension/Collection+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift similarity index 94% rename from Sources/OpenSwiftUICore/Extension/Collection+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift index 0b7a0dca1..d8e80f886 100644 --- a/Sources/OpenSwiftUICore/Extension/Collection+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift @@ -2,9 +2,10 @@ // Collection+Extension.swift // OpenSwiftUICore // -// Audited for iOS 18.0 // Status: Complete +// MARK: - Collection + Index Extension [6.0.87] + extension Collection { package func index(atOffset n: Int) -> Index { index(startIndex, offsetBy: n) diff --git a/Sources/OpenSwiftUICore/Extension/Comparable+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Comparable+Extension.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/Comparable+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/Comparable+Extension.swift diff --git a/Sources/OpenSwiftUICore/Extension/FloatingPoint+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift similarity index 98% rename from Sources/OpenSwiftUICore/Extension/FloatingPoint+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift index 30205062d..6acc68437 100644 --- a/Sources/OpenSwiftUICore/Extension/FloatingPoint+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift @@ -2,9 +2,12 @@ // FloatingPoint+Extension.swift // OpenSwiftUICore // -// Audited for iOS 18.0 // Status: Complete +package import Foundation + +// MARK: - FloatingPoint + Zero [6.0.87] + extension FloatingPoint { /// Determines whether two floating-point values are approximately equal within a specified tolerance. /// diff --git a/Sources/OpenSwiftUICore/Extension/OptionSet+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/OptionSet+Extension.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/OptionSet+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/OptionSet+Extension.swift diff --git a/Sources/OpenSwiftUICore/Extension/Round+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Round+Extension.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/Round+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/Round+Extension.swift diff --git a/Sources/OpenSwiftUICore/Extension/Unmanaged+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Unmanaged+Extension.swift similarity index 100% rename from Sources/OpenSwiftUICore/Extension/Unmanaged+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/Unmanaged+Extension.swift diff --git a/Sources/OpenSwiftUICore/Extension/UnsafePointer+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift similarity index 99% rename from Sources/OpenSwiftUICore/Extension/UnsafePointer+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift index 91290cf53..50e991d43 100644 --- a/Sources/OpenSwiftUICore/Extension/UnsafePointer+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift @@ -47,3 +47,4 @@ extension UnsafeMutableBufferPointer { baseAddress ?? .null } } + diff --git a/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift b/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift new file mode 100644 index 000000000..ff2aebf4e --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift @@ -0,0 +1,843 @@ +// +// StandardLibraryAdditions.swift +// OpenSwiftUICore +// +// Status: Complete +// ID: DE8DAFA613257BEA44770487175C185C (SwiftUICore) + +package import Foundation +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#else +#error("Unsupported Platform") +#endif + +// MARK: - bind [6.5.4] + +package func bind(_ action: ((T) -> Void)?, _ value: T) -> (() -> Void)? { + guard let action else { + return nil + } + return { action(value) } +} + +// MARK: - FloatingPoint + Misc [6.5.4] + +extension Float { + package func mix(with other: Float, by t: Double) -> Float { + (other - self) * Float(t) + self + } +} + +extension CGFloat { + package func mix(with other: CGFloat, by t: Double) -> CGFloat { + (other - self) * CGFloat(t) + self + } +} + +extension Double { + package func mix(with other: Double, by t: Double) -> Double { + (other - self) * t + self + } +} + +extension Double { + package var quantized: Double { + CGFloat(self).quantized + } +} + +extension Float { + package var quantized: Float { + #if canImport(Darwin) + Darwin.round(self * 256.0) / 256.0 + #elseif canImport(Glibc) + Glibc.round(self * 256.0) / 256.0 + #else + #error("Unsupported Platform") + #endif + } +} + +extension CGFloat { + package var quantized: CGFloat { + #if canImport(Darwin) + Darwin.round(self * 256.0) / 256.0 + #elseif canImport(Glibc) + Glibc.round(self * 256.0) / 256.0 + #else + #error("Unsupported Platform") + #endif + } +} + +extension FloatingPoint { + package func mappingNaN(to value: Self) -> Self { + isNaN ? value : self + } +} + +extension BinaryFloatingPoint { + package func ensuringNonzeroValue() -> Self { + isZero ? Self.leastNonzeroMagnitude : self + } +} + +// MARK: - unsafeIncrement [6.5.4] + +extension UInt32 { + package mutating func unsafeIncrement() { + self = self &+ 1 + } +} + +// MARK: - FixedWidthInteger + clamping [6.5.4] + +extension FixedWidthInteger { + package init(clamping value: T) where T: BinaryFloatingPoint { + self.init(value.clamp(min: T(Self.min), max: T(Self.max))) + } +} + +// MARK: - Duration Conversion [6.5.4] + +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) +extension Double { + package init(_ duration: Duration) { + let (seconds, attoseconds) = duration.components + self = Double(seconds) + Double(attoseconds) / 1e18 + } +} + +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) +package func abs(_ duration: Duration) -> Duration { + (duration < .zero) ? (.zero - duration) : duration +} + +// MARK: - Date + Extension [6.5.4] + +extension Date { + package var nextUp: Date { + Date(timeIntervalSinceReferenceDate: timeIntervalSinceReferenceDate.nextUp) + } + + package var nextDown: Date { + Date(timeIntervalSinceReferenceDate: timeIntervalSinceReferenceDate.nextDown) + } +} + +// MARK: - Pairt [6.5.4] + +package struct Pair { + package var first: First + package var second: Second + + package init(_ first: First, _ second: Second) { + self.first = first + self.second = second + } + + private enum CodingKeys: CodingKey { + case first + case second + } +} + +extension Pair: Equatable where First: Equatable, Second: Equatable { + package static func == (a: Pair, b: Pair) -> Bool { + return a.first == b.first && a.second == b.second + } +} + +extension Pair: Hashable where First: Hashable, Second: Hashable { + package func hash(into hasher: inout Hasher) { + hasher.combine(first) + hasher.combine(second) + } + + package var hashValue: Int { + var hasher = Hasher() + hash(into: &hasher) + return hasher.finalize() + } +} + +extension Pair: Codable where First: Decodable, First: Encodable, Second: Decodable, Second: Encodable { + package func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(first, forKey: .first) + try container.encode(second, forKey: .second) + } + + package init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + first = try container.decode(First.self, forKey: .first) + second = try container.decode(Second.self, forKey: .second) + } +} + +// MARK: - ArrayID [6.5.4] + +package struct ArrayID: Hashable { + private let objectIdentifier: ObjectIdentifier + + package init(_ items: [T]) { + self.objectIdentifier = ObjectIdentifier(items as AnyObject) + } +} + +// MARK: - address(of:) [6.5.4] + +package func address(of object: AnyObject) -> UnsafeRawPointer { + unsafeBitCast(object, to: UnsafeRawPointer.self) +} + +// MARK: - UnsafeMutableBufferProjectionPointer [6.5.4] + +package struct UnsafeMutableBufferProjectionPointer: RandomAccessCollection, MutableCollection { + package var startIndex: Int { 0 } + + private let _start: UnsafeMutableRawPointer + + package let endIndex: Int + + @inline(__always) + package init() { + _start = UnsafeMutableRawPointer(mutating: UnsafePointer.null) + endIndex = 0 + } + + @inline(__always) + package init(start: UnsafeMutablePointer, count: Int) { + _start = UnsafeMutableRawPointer(start) + endIndex = count + } + + @inline(__always) + package init( + _ base: UnsafeMutableBufferPointer, + _ keyPath: WritableKeyPath + ) { + if base.isEmpty { + _start = UnsafeMutableRawPointer(mutating: UnsafePointer.null) + } else { + // FIXME: We should use a more safer call. eg. swift_modifyAtWritableKeyPath + _start = UnsafeMutableRawPointer(base.baseAddress!.pointer(to: keyPath)!) + } + endIndex = base.count + } + + package subscript(i: Int) -> Subject { + @_transparent + unsafeAddress { + UnsafeRawPointer(_start) + .advanced(by: MemoryLayout.stride * i) + .assumingMemoryBound(to: Subject.self) + } + @_transparent + nonmutating unsafeMutableAddress { + _start + .advanced(by: MemoryLayout.stride * i) + .assumingMemoryBound(to: Subject.self) + } + } + + package typealias Element = Subject +} + +// MARK: - Numeric Extension [6.5.4] + +extension Numeric { + package var isNaN: Bool { + self != self + } + + package var isFinite: Bool { + (self - self) == 0 + } +} + +// MARK: - Sequence.first(ofType:) [6.5.4] + +extension Sequence { + package func first(ofType: T.Type) -> T? { + first { $0 is T } as? T + } +} + +// MARK: - Collection + prefix and suffix [6.5.4] + +extension Collection where Self.Element: Equatable { + package func commonPrefix(with other: Other) -> (Self.SubSequence, Other.SubSequence) where Other: Collection, Element == Other.Element { + var selfIndex = startIndex + var otherIndex = other.startIndex + + while selfIndex != endIndex && otherIndex != other.endIndex && self[selfIndex] == other[otherIndex] { + formIndex(after: &selfIndex) + other.formIndex(after: &otherIndex) + } + + return (self[startIndex ..< selfIndex], other[other.startIndex ..< otherIndex]) + } +} + +extension BidirectionalCollection where Self.Element: Equatable { + package func commonSuffix(with other: Other) -> (Self.SubSequence, Other.SubSequence) where Other: BidirectionalCollection, Self.Element == Other.Element { + var selfIndex = endIndex + var otherIndex = other.endIndex + + while selfIndex != startIndex && otherIndex != other.startIndex { + formIndex(before: &selfIndex) + other.formIndex(before: &otherIndex) + + if self[selfIndex] != other[otherIndex] { + formIndex(after: &selfIndex) + other.formIndex(after: &otherIndex) + break + } + } + + return (self[selfIndex ..< endIndex], other[otherIndex ..< other.endIndex]) + } +} + +// MARK: - CountingIndexCollection [6.5.4] + +package struct CountingIndexCollection where Base: BidirectionalCollection { + package let base: Base + + package init(_ base: Base) { + self.base = base + } +} + +extension CountingIndexCollection: BidirectionalCollection { + package typealias Index = CountingIndex + package typealias Element = Base.Element + + package var startIndex: CountingIndexCollection.Index { + CountingIndex(base: base.startIndex, offset: base.isEmpty ? nil : 0) + } + + package var endIndex: CountingIndexCollection.Index { + CountingIndex(base: base.endIndex, offset: nil) + } + + package func index(before i: CountingIndexCollection.Index) -> CountingIndexCollection.Index { + let newBase = base.index(before: i.base) + guard newBase != base.startIndex else { + return CountingIndex(base: newBase, offset: nil) + } + let newOffset = i.offset! - 1 + return CountingIndex(base: newBase, offset: newOffset) + } + + package func index(after i: CountingIndexCollection.Index) -> CountingIndexCollection.Index { + let newBase = base.index(after: i.base) + guard newBase != base.endIndex else { + return CountingIndex(base: newBase, offset: nil) + } + let newOffset = i.offset! + 1 + return CountingIndex(base: newBase, offset: newOffset) + } + + package func index( + _ i: CountingIndex, + offsetBy distance: Int, + limitedBy limit: CountingIndex + ) -> CountingIndex? { + guard let newBase = base.index(i.base, offsetBy: distance, limitedBy: limit.base) else { + return nil + } + guard newBase != base.endIndex else { + return CountingIndex(base: newBase, offset: nil) + } + let newOffset = i.offset! + distance + return CountingIndex(base: newBase, offset: newOffset) + } + + package subscript(position: CountingIndexCollection.Index) -> CountingIndexCollection.Element { + base[position.base] + } + + package typealias Indices = DefaultIndices> + package typealias Iterator = IndexingIterator> + package typealias SubSequence = Slice> +} + +// MARK: - CountingIndex [6.5.4] + +package struct CountingIndex: Equatable where Base: Comparable { + package let base: Base + package let offset: Int? + + package init(base: Base, offset: Int?) { + self.base = base + self.offset = offset + } +} + +extension CountingIndex: Comparable { + package static func < (lhs: CountingIndex, rhs: CountingIndex) -> Bool { + return lhs.base < rhs.base + } +} + +extension CountingIndex: CustomStringConvertible { + package var description: String { + "(base: \(base) | offset: \(offset?.description ?? "nil"))" + } +} + +// MARK: - 4 elements equal [6.5.4] + +package func == ( + lhs: ((A, B), (C, D)), + rhs: ((A, B), (C, D)) +) -> Bool where A: Equatable, B: Equatable, C: Equatable, D: Equatable { + return lhs.0.0 == rhs.0.0 && lhs.0.1 == rhs.0.1 && lhs.1.0 == rhs.1.0 && lhs.1.1 == rhs.1.1 +} + +// MARK: - Optional + if-then [6.5.4] + +extension Optional { + package init(if condition: Bool, then value: @autoclosure () -> Wrapped) { + self = condition ? value() : nil + } +} + +// MARK: - min and max with optional [6.5.4] + +package func min(_ a: C, ifPresent b: C?) -> C where C: Comparable { + guard let b else { return a } + return Swift.min(a, b) +} + +package func max(_ a: C, ifPresent b: C?) -> C where C: Comparable { + guard let b else { return a } + return Swift.max(a, b) +} + +// MARK: - IndirectOptional [6.5.4] + +@propertyWrapper +package enum IndirectOptional: ExpressibleByNilLiteral { + case none + indirect case some(Wrapped) + + package init(_ value: Wrapped) { + self = .some(value) + } + + package init(nilLiteral: ()) { + self = .none + } + + package init(wrappedValue: Wrapped?) { + if let value = wrappedValue { + self = .some(value) + } else { + self = .none + } + } + + package var wrappedValue: Wrapped? { + switch self { + case .none: nil + case let .some(wrapped): wrapped + } + } +} + +extension IndirectOptional: Equatable where Wrapped: Equatable {} + +extension IndirectOptional: Hashable where Wrapped: Hashable {} + +// MARK: - Cache 3 [6.0.87] + +/// A simple fixed-size cache that stores up to three key-value pairs. +/// +/// Cache3 provides a lightweight, efficient cache implementation with LRU (Least Recently Used) +/// eviction behavior. When a new item is added to a full cache, the oldest item is evicted. +/// +/// Example usage: +/// +/// var cache = Cache3() +/// cache.put("one", value: 1) +/// cache.put("two", value: 2) +/// let value = cache.get("three") { 3 } // Creates and caches value 3 +/// +package struct Cache3 where Key: Equatable { + /// Internal tuple-based storage for the cached items. + /// The first element represents the most recently used item. + var store: ((key: Key, value: Value)?, (key: Key, value: Value)?, (key: Key, value: Value)?) + + /// Creates a new empty cache. + package init() { + self.store = (nil, nil, nil) + } + + /// Looks up a value in the cache by key without changing cache order. + /// + /// - Parameter key: The key to look up. + /// - Returns: The value associated with the key, or `nil` if the key is not in the cache. + @inline(__always) + package func find(_ key: Key) -> Value? { + if let item = store.0, item.key == key { + return item.value + } + if let item = store.1, item.key == key { + return item.value + } + if let item = store.2, item.key == key { + return item.value + } + return nil + } + + /// Inserts a new value into the cache with the specified key. + /// + /// This method adds a new key-value pair to the cache, making it the most recently used item. + /// If the cache already has 3 items, the least recently used item is evicted. + /// + /// - Parameters: + /// - key: The key to associate with the value. + /// - value: The value to cache. + @inline(__always) + package mutating func put(_ key: Key, value: Value) { + store = ((key, value), store.0, store.1) + } + + /// Retrieves a value from the cache by key, creating it if not present. + /// + /// This method first checks if the key exists in the cache. If found, it returns the + /// associated value. If not found, it calls the provided closure to create a new value, + /// caches it, and returns the newly created value. + /// + /// - Parameters: + /// - key: The key to look up. + /// - makeValue: A closure that creates a new value if the key is not found. + /// - Returns: The value associated with the key, either retrieved from cache or newly created. + @inline(__always) + package mutating func get(_ key: Key, makeValue: () -> Value) -> Value { + guard let value = find(key) else { + let value = makeValue() + put(key, value: value) + return value + } + return value + } +} + +// MARK: - Dictionary Extensions [6.5.4] + +extension Dictionary { + package func optimisticFilter(_ predicate: (Element) -> Bool) -> [Key: Value] { + guard count > 64 else { + return filter(predicate) + } + // FIXME: Use a more efficient approach for larger dictionaries + var result = [Key: Value]() + for (key, value) in self { + if predicate((key, value)) { + result[key] = value + } + } + return result + } + + package init(identifying items: some Sequence, by identifier: (Value) -> Key) { + self.init() + for item in items { + let key = identifier(item) + self[key] = item + } + } +} + +// MARK: - Environment [6.5.4] + +package func readEnvironment(_ value: inout Bool?, _ key: UnsafePointer) -> Bool { + if let existing = value { + return existing + } + guard let env = getenv(key) else { + return false + } + let result = atoi(env) != 0 + value = result + return result +} + +// MARK: - BidirectionalCollection Extensions [6.5.4] [WIP] + +extension BidirectionalCollection where Self: MutableCollection, Element: Comparable { + package mutating func formNextLexicographicalPermutation() -> Bool { + preconditionFailure("TODO") + } +} + +// MARK: - RandomAccessCollection Extensions [Copilot] + +extension RandomAccessCollection { + package func lowerBound(_ predicate: (Element) -> Bool) -> Index { + var left = startIndex + var right = endIndex + + while left < right { + let mid = index(left, offsetBy: distance(from: left, to: right) / 2) + if predicate(self[mid]) { + right = mid + } else { + left = index(after: mid) + } + } + return left + } +} + +extension RandomAccessCollection where Element: Comparable { + package func lowerBound(of value: Element) -> Index { + lowerBound { $0 >= value } + } +} + +// MARK: - Range Extensions [Copilot] + +extension Range { + package func intersection(_ other: Range) -> Range? { + let lower = Swift.max(lowerBound, other.lowerBound) + let upper = Swift.min(upperBound, other.upperBound) + return lower < upper ? lower..) -> Bool { + return lowerBound <= other.lowerBound && other.upperBound <= upperBound + } +} + +extension Range where Bound: Numeric { + package var length: Bound { + upperBound - lowerBound + } +} + +extension Range where Bound: SignedNumeric { + package func offset(by delta: Bound) -> Range { + return (lowerBound + delta)..<(upperBound + delta) + } +} + +// MARK: - ClosedRange Extensions [Copilot] + +extension ClosedRange { + package init(bounds a: Bound, _ b: Bound) { + if a <= b { + self = a...b + } else { + self = b...a + } + } + + package func union(_ other: ClosedRange) -> ClosedRange { + let lower = Swift.min(lowerBound, other.lowerBound) + let upper = Swift.max(upperBound, other.upperBound) + return lower...upper + } + + package func intersection(_ other: ClosedRange) -> ClosedRange? { + let lower = Swift.max(lowerBound, other.lowerBound) + let upper = Swift.min(upperBound, other.upperBound) + return lower <= upper ? lower...upper : nil + } + + package func contains(_ other: ClosedRange) -> Bool { + return lowerBound <= other.lowerBound && other.upperBound <= upperBound + } +} + +extension ClosedRange where Bound: Numeric { + package var length: Bound { + upperBound - lowerBound + } + + package static func + (lhs: ClosedRange, rhs: Bound) -> ClosedRange { + return (lhs.lowerBound + rhs)...(lhs.upperBound + rhs) + } + + package static func - (lhs: ClosedRange, rhs: Bound) -> ClosedRange { + return (lhs.lowerBound - rhs)...(lhs.upperBound - rhs) + } + + package static func += (lhs: inout ClosedRange, rhs: Bound) { + lhs = lhs + rhs + } + + package static func -= (lhs: inout ClosedRange, rhs: Bound) { + lhs = lhs - rhs + } +} + +extension ClosedRange where Bound: SignedNumeric { + package func offset(by delta: Bound) -> ClosedRange { + return (lowerBound + delta)...(upperBound + delta) + } +} + +extension ClosedRange where Bound == Date { + package func progress(at date: Date, countdown: Bool) -> Double { + let totalDuration = upperBound.timeIntervalSince(lowerBound) + guard totalDuration > 0 else { return countdown ? 1.0 : 0.0 } + + let elapsed = date.timeIntervalSince(lowerBound) + let progress = elapsed / totalDuration + let clampedProgress = Swift.max(0.0, Swift.min(1.0, progress)) + + return countdown ? (1.0 - clampedProgress) : clampedProgress + } +} + +// MARK: - CollectionOfTwo [6.5.4] + +package struct CollectionOfTwo: RandomAccessCollection, MutableCollection { + package var startIndex: Int { 0 } + + package var endIndex: Int { 2 } + + package var elements: (T, T) + + package init(_ first: T, _ second: T) { + self.elements = (first, second) + } + + package subscript(i: Int) -> T { + get { + switch i { + case 0: return elements.0 + case 1: return elements.1 + default: preconditionFailure("index out of range") + } + } + set { + switch i { + case 0: elements.0 = newValue + case 1: elements.1 = newValue + default: preconditionFailure("index out of range") + } + } + } +} + +// MARK: - Protocol Conformance [6.5.4] + +package func conformsToProtocol(_ type: any Any.Type, _ desc: UnsafeRawPointer) -> Bool { + swiftConformsToProtocol(type, desc) != nil +} + +// MARK: - String Extensions [6.5.4] + +extension String { + package var isNewLineOrReturn: Bool { + self == "\n" || self == "\r" + } +} + +// MARK: - DefaultStringInterpolation Extensions [6.5.4] + +private let roundingFormatter = { + let formatter = NumberFormatter() + formatter.minimumFractionDigits = 1 + formatter.maximumFractionDigits = 3 + return formatter +}() + +extension BinaryFloatingPoint { + @inline(__always) + fileprivate static func exp10(_ x: Self) -> Double { + #if canImport(Darwin) + return __exp10(Double(x)) + #else + pow(10, Double(x)) + #endif + } + + fileprivate func roundedForDisplay() -> Self { + let multiplier = Double.exp10(Double(roundingFormatter.maximumFractionDigits)) + let result = Self((multiplier * Double(self)).rounded() / multiplier) + return result == 0 ? 0 : result + } +} + +extension DefaultStringInterpolation { + package mutating func appendInterpolation(rounding value: Float) { + appendLiteral(roundingFormatter.string(from: NSNumber(value: value.roundedForDisplay()))!) + } + + package mutating func appendInterpolation(rounding value: Double) { + appendLiteral(roundingFormatter.string(from: NSNumber(value: value.roundedForDisplay()))!) + } + + package mutating func appendInterpolation(rounding value: SIMD2) { + appendLiteral("(\(rounding: value.x), \(rounding: value.y))") + } + + package mutating func appendInterpolation(rounding value: SIMD3) { + appendLiteral("(\(rounding: value.x), \(rounding: value.y), \(rounding: value.z))") + } + + package mutating func appendInterpolation(rounding value: SIMD4) { + appendLiteral("(\(rounding: value.x), \(rounding: value.y), \(rounding: value.z), \(rounding: value.w)") + } +} + +// MARK: - Sequence Extensions [6.5.4] + +extension Sequence { + package func sorted(by keyPath: KeyPath) -> [Element] { + sorted { lhs, rhs in + lhs[keyPath: keyPath] < rhs[keyPath: keyPath] + } + } +} + +// MARK: - Array Extensions [6.5.4] + +extension Array { + package mutating func sort(by keyPath: KeyPath, reversed: Bool = false) { + sort { lhs, rhs in + if reversed { + lhs[keyPath: keyPath] > rhs[keyPath: keyPath] + } else { + lhs[keyPath: keyPath] < rhs[keyPath: keyPath] + } + } + } +} + +extension Array where Element: Hashable { + package func removingDuplicates() -> [Element] { + var dict = [Element: Bool]() + return filter { dict.updateValue(true, forKey: $0) == nil } + } + + package mutating func removeDuplicates() { + self = removingDuplicates() + } +} + +// MARK: - EquatableOptionalObject [6.5.4] + +@propertyWrapper +package struct EquatableOptionalObject: Equatable where T: AnyObject { + package var wrappedValue: T? + + package init(wrappedValue: T?) { + self.wrappedValue = wrappedValue + } + + package static func == (lhs: EquatableOptionalObject, rhs: EquatableOptionalObject) -> Bool { + return lhs.wrappedValue === rhs.wrappedValue + } +} diff --git a/Sources/OpenSwiftUICore/Util/Utils.swift b/Sources/OpenSwiftUICore/Util/UIUtils.swift similarity index 81% rename from Sources/OpenSwiftUICore/Util/Utils.swift rename to Sources/OpenSwiftUICore/Util/UIUtils.swift index e14edcf75..16caf197b 100644 --- a/Sources/OpenSwiftUICore/Util/Utils.swift +++ b/Sources/OpenSwiftUICore/Util/UIUtils.swift @@ -1,5 +1,5 @@ // -// Utils.swift +// UIUtils.swift // OpenSwiftUICore // // Audited for iOS 18.0 @@ -7,14 +7,6 @@ import OpenSwiftUI_SPI -@inlinable -@inline(__always) -func asOptional(_ value: Value) -> Value? { - func unwrap() -> T { value as! T } - let optionalValue: Value? = unwrap() - return optionalValue -} - #if canImport(Darwin) // NOTE: use runtime check instead of #if targetEnvironment(macCatalyst) diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/Cache3Tests.swift b/Tests/OpenSwiftUICoreTests/Data/Util/Cache3Tests.swift deleted file mode 100644 index ef7ddd4ea..000000000 --- a/Tests/OpenSwiftUICoreTests/Data/Util/Cache3Tests.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// Cache3Tests.swift -// OpenSwiftUICoreTests - -@testable import OpenSwiftUICore -import Testing - -struct Cache3Tests { - @Test - func put() { - var cache: Cache3 = Cache3() - cache.put(1, value: "1") - #expect(cache.find(1) == "1") - #expect(cache.find(2) == nil) - #expect(cache.find(3) == nil) - #expect(cache.find(4) == nil) - - cache.put(2, value: "2") - #expect(cache.find(1) == "1") - #expect(cache.find(2) == "2") - #expect(cache.find(3) == nil) - #expect(cache.find(4) == nil) - - cache.put(3, value: "3") - #expect(cache.find(1) == "1") - #expect(cache.find(2) == "2") - #expect(cache.find(3) == "3") - #expect(cache.find(4) == nil) - - cache.put(4, value: "4") - #expect(cache.find(1) == nil) - #expect(cache.find(2) == "2") - #expect(cache.find(3) == "3") - #expect(cache.find(4) == "4") - } - - @Test - func get() { - var cache: Cache3 = Cache3() - - let value4 = cache.get(4) { "4" } - #expect(value4 == "4") - #expect(cache.find(1) == nil) - #expect(cache.find(2) == nil) - #expect(cache.find(3) == nil) - #expect(cache.find(4) == "4") - - let value3 = cache.get(3) { "3" } - #expect(value3 == "3") - #expect(cache.find(1) == nil) - #expect(cache.find(2) == nil) - #expect(cache.find(3) == "3") - #expect(cache.find(4) == "4") - - let value2 = cache.get(2) { "2" } - #expect(value2 == "2") - #expect(cache.find(1) == nil) - #expect(cache.find(2) == "2") - #expect(cache.find(3) == "3") - #expect(cache.find(4) == "4") - - let value1 = cache.get(1) { "1" } - #expect(value1 == "1") - #expect(cache.find(1) == "1") - #expect(cache.find(2) == "2") - #expect(cache.find(3) == "3") - #expect(cache.find(4) == nil) - } -} diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/AnyHashable2Tests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/AnyHashable2Tests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/AnyHashable2Tests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/AnyHashable2Tests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/BitVector64Tests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/BitVector64Tests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/BitVector64Tests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/BitVector64Tests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/BitVectorTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/BitVectorTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/BitVectorTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/BitVectorTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/BloomFilterTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/BloomFilterTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/BloomFilterTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/BloomFilterTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/BoxTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/BoxTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/BoxTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/BoxTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift new file mode 100644 index 000000000..0d5d4622a --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift @@ -0,0 +1,7 @@ +// +// Cache3Tests.swift +// OpenSwiftUICoreTests + +@testable import OpenSwiftUICore +import Testing + diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/ConcatenatedCollectionTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/ConcatenatedCollectionTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/ConcatenatedCollectionTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/ConcatenatedCollectionTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/InlineArrayTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/InlineArrayTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/InlineArrayTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/InlineArrayTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/ObjectCacheTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/ObjectCacheTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/ObjectCacheTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/ObjectCacheTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/StackTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/StackTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/StackTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/StackTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/UniqueIDTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/UniqueIDTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/UniqueIDTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/UniqueIDTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Data/Util/VersionSeedTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/VersionSeedTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/VersionSeedTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/VersionSeedTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Extension/CGAffineTransform+ExtensionTests.swift b/Tests/OpenSwiftUICoreTests/Util/Extension/CGAffineTransform+ExtensionTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Extension/CGAffineTransform+ExtensionTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Extension/CGAffineTransform+ExtensionTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Extension/CGRect+ExtensionTests.swift b/Tests/OpenSwiftUICoreTests/Util/Extension/CGRect+ExtensionTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Extension/CGRect+ExtensionTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Extension/CGRect+ExtensionTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Util/Extension/Collection+ExtensionTests.swift b/Tests/OpenSwiftUICoreTests/Util/Extension/Collection+ExtensionTests.swift new file mode 100644 index 000000000..76ddaad1a --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/Extension/Collection+ExtensionTests.swift @@ -0,0 +1,190 @@ +// +// Collection+ExtensionTests.swift +// OpenSwiftUICoreTests + +import Testing +@testable import OpenSwiftUICore + +// MARK: - Collection Extension Tests + +struct Collection_ExtensionTests { + + // MARK: - Index Extension Tests + + @Test + func indexAtOffset() { + let string = "Hello" + let index = string.index(atOffset: 2) + + #expect(string[index] == "l") + } + + @Test + func indexAtOffsetLimited() { + let string = "Hello" + let limitIndex = string.index(string.startIndex, offsetBy: 3) + let result = string.index(atOffset: 5, limitedBy: limitIndex) + + #expect(result == nil) + } + + @Test + func offsetOfIndex() { + let string = "Hello" + let index = string.index(string.startIndex, offsetBy: 2) + let offset = string.offset(of: index) + + #expect(offset == 2) + } + + @Test + func safeSubscriptValid() { + let string = "Hello" + let index = string.index(string.startIndex, offsetBy: 1) + let result = string[safe: index] + + #expect(result == "e") + } + + @Test + func safeSubscriptInvalid() { + let string = "Hello" + let result = string[safe: string.endIndex] + + #expect(result == nil) + } + + // MARK: - Common Prefix Tests + + @Test + func commonPrefixIdenticalStrings() { + let str1 = "hello" + let str2 = "hello" + let (prefix1, prefix2) = str1.commonPrefix(with: str2) + + #expect(String(prefix1) == "hello") + #expect(String(prefix2) == "hello") + } + + @Test + func commonPrefixPartialMatch() { + let str1 = "hello world" + let str2 = "hello swift" + let (prefix1, prefix2) = str1.commonPrefix(with: str2) + + #expect(String(prefix1) == "hello ") + #expect(String(prefix2) == "hello ") + } + + @Test + func commonPrefixNoMatch() { + let str1 = "apple" + let str2 = "banana" + let (prefix1, prefix2) = str1.commonPrefix(with: str2) + + #expect(String(prefix1) == "") + #expect(String(prefix2) == "") + } + + @Test + func commonPrefixEmptyStrings() { + let str1 = "" + let str2 = "" + let (prefix1, prefix2) = str1.commonPrefix(with: str2) + + #expect(String(prefix1) == "") + #expect(String(prefix2) == "") + } + + @Test + func commonPrefixOneEmpty() { + let str1 = "hello" + let str2 = "" + let (prefix1, prefix2) = str1.commonPrefix(with: str2) + + #expect(String(prefix1) == "") + #expect(String(prefix2) == "") + } + + @Test + func commonPrefixDifferentLengths() { + let str1 = "test" + let str2 = "testing" + let (prefix1, prefix2) = str1.commonPrefix(with: str2) + + #expect(String(prefix1) == "test") + #expect(String(prefix2) == "test") + } + + // MARK: - Common Suffix Tests + + @Test + func commonSuffixIdenticalStrings() { + let str1 = "hello" + let str2 = "hello" + let (suffix1, suffix2) = str1.commonSuffix(with: str2) + + #expect(String(suffix1) == "hello") + #expect(String(suffix2) == "hello") + } + + @Test + func commonSuffixPartialMatch() { + let str1 = "world hello" + let str2 = "swift hello" + let (suffix1, suffix2) = str1.commonSuffix(with: str2) + + #expect(String(suffix1) == " hello") + #expect(String(suffix2) == " hello") + } + + @Test + func commonSuffixNoMatch() { + let str1 = "apple" + let str2 = "banana" + let (suffix1, suffix2) = str1.commonSuffix(with: str2) + + #expect(String(suffix1) == "") + #expect(String(suffix2) == "") + } + + @Test + func commonSuffixEmptyStrings() { + let str1 = "" + let str2 = "" + let (suffix1, suffix2) = str1.commonSuffix(with: str2) + + #expect(String(suffix1) == "") + #expect(String(suffix2) == "") + } + + @Test + func commonSuffixOneEmpty() { + let str1 = "hello" + let str2 = "" + let (suffix1, suffix2) = str1.commonSuffix(with: str2) + + #expect(String(suffix1) == "") + #expect(String(suffix2) == "") + } + + @Test + func commonSuffixDifferentLengths() { + let str1 = "testing" + let str2 = "ing" + let (suffix1, suffix2) = str1.commonSuffix(with: str2) + + #expect(String(suffix1) == "ing") + #expect(String(suffix2) == "ing") + } + + @Test + func commonSuffixComplexCase() { + let str1 = "filename.txt" + let str2 = "document.txt" + let (suffix1, suffix2) = str1.commonSuffix(with: str2) + + #expect(String(suffix1) == ".txt") + #expect(String(suffix2) == ".txt") + } +} diff --git a/Tests/OpenSwiftUICoreTests/Extension/FloatingPoint+ExtensionTests.swift b/Tests/OpenSwiftUICoreTests/Util/Extension/FloatingPoint+ExtensionTests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Extension/FloatingPoint+ExtensionTests.swift rename to Tests/OpenSwiftUICoreTests/Util/Extension/FloatingPoint+ExtensionTests.swift diff --git a/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift b/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift new file mode 100644 index 000000000..fadebc76b --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift @@ -0,0 +1,459 @@ +// +// StandardLibraryAdditionsTests.swift +// OpenSwiftUICoreTests + +import OpenSwiftUICore +import Testing + +// MARK: - UnsafeMutableBufferProjectionPointerTests + +struct UnsafeMutableBufferProjectionPointerTests { + private struct TestScene { + var x: Int + var y: Double + var z: String + } + + @Test + func emptyInitialization() { + let pointer: UnsafeMutableBufferProjectionPointer = UnsafeMutableBufferProjectionPointer() + + #expect(pointer.startIndex == 0) + #expect(pointer.endIndex == 0) + #expect(pointer.isEmpty) + } + + @Test + func directPointerInitialization() { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 3) + defer { buffer.deallocate() } + + buffer[0] = 10 + buffer[1] = 20 + buffer[2] = 30 + + let pointer = UnsafeMutableBufferProjectionPointer(start: buffer.baseAddress!, count: 3) + + #expect(pointer.startIndex == 0) + #expect(pointer.endIndex == 3) + #expect(pointer.count == 3) + #expect(pointer[0] == 10) + #expect(pointer[1] == 20) + #expect(pointer[2] == 30) + } + + @Test + func keyPathProjectionWithEmptyBuffer() { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 0) + defer { buffer.deallocate() } + + let pointer = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.x) + + #expect(pointer.startIndex == 0) + #expect(pointer.endIndex == 0) + #expect(pointer.isEmpty) + } + + @Test + func bufferProjection() { + var scenes = [ + TestScene(x: 1, y: 1.0, z: "1"), + TestScene(x: 2, y: 2.0, z: "2"), + ] + scenes.withUnsafeMutableBufferPointer { base in + let xProjection = UnsafeMutableBufferProjectionPointer(base, \TestScene.x) + #expect(xProjection[0] == 1) + #expect(xProjection[1] == 2) + + let yProjection = UnsafeMutableBufferProjectionPointer(base, \TestScene.y) + #expect(yProjection[0] == 1.0) + #expect(yProjection[1] == 2.0) + + let zProjection = UnsafeMutableBufferProjectionPointer(base, \TestScene.z) + #expect(zProjection[0] == "1") + #expect(zProjection[1] == "2") + + xProjection[1] = 3 + } + + #expect(scenes[1].x == 3) + } +} + +// MARK: - CountingIndexCollectionTests + +struct CountingIndexCollectionTests { + @Test + func emptyCollection() { + let base: [Int] = [] + let counting = CountingIndexCollection(base) + + #expect(counting.isEmpty) + #expect(counting.startIndex.offset == nil) + #expect(counting.endIndex.offset == nil) + } + + @Test + func singleElementCollection() { + let base = [42] + let counting = CountingIndexCollection(base) + + #expect(counting.count == 1) + #expect(counting.startIndex.offset == 0) + #expect(counting.endIndex.offset == nil) + #expect(counting[counting.startIndex] == 42) + } + + @Test + func multipleElementsCollection() { + let base = [10, 20, 30, 40] + let counting = CountingIndexCollection(base) + + #expect(counting.count == 4) + #expect(counting.startIndex.offset == 0) + #expect(counting.endIndex.offset == nil) + + var index = counting.startIndex + #expect(counting[index] == 10) + #expect(index.offset == 0) + + index = counting.index(after: index) + #expect(counting[index] == 20) + #expect(index.offset == 1) + + index = counting.index(after: index) + #expect(counting[index] == 30) + #expect(index.offset == 2) + + index = counting.index(after: index) + #expect(counting[index] == 40) + #expect(index.offset == 3) + + index = counting.index(after: index) + #expect(index == counting.endIndex) + #expect(index.offset == nil) + } + + @Test + func bidirectionalIteration() { + let base = ["a", "b", "c"] + let counting = CountingIndexCollection(base) + + var index = counting.index(atOffset: 2) + + index = counting.index(before: index) + #expect(counting[index] == "b") + #expect(index.offset == 1) + + index = counting.index(before: index) + #expect(counting[index] == "a") + #expect(index.offset == nil) + } + + @Test + func offsetByLimitedMethod() { + let base = [1, 2, 3] + let counting = CountingIndexCollection(base) + + let startIndex = counting.startIndex + let limitIndex = counting.index(after: startIndex) + + let result = counting.index(startIndex, offsetBy: 3, limitedBy: limitIndex) + #expect(result == nil) + + let validResult = counting.index(startIndex, offsetBy: 1, limitedBy: limitIndex) + #expect(validResult?.offset == 1) + } + + @Test + func iterationWithForLoop() { + let base = [10, 20, 30] + let counting = CountingIndexCollection(base) + + var elements: [Int] = [] + var offsets: [Int?] = [] + + for element in counting { + elements.append(element) + } + + for index in counting.indices { + offsets.append(index.offset) + } + + #expect(elements == [10, 20, 30]) + #expect(offsets == [0, 1, 2]) + } + + @Test + func stringCollection() { + let base = "hello" + let counting = CountingIndexCollection(base) + + #expect(counting.count == 5) + + var characters: [Character] = [] + for char in counting { + characters.append(char) + } + + #expect(characters == ["h", "e", "l", "l", "o"]) + } +} + +// MARK: - CountingIndexTests + +struct CountingIndexTests { + @Test + func equality() { + let index1 = CountingIndex(base: 10, offset: 5) + let index2 = CountingIndex(base: 10, offset: 5) + let index3 = CountingIndex(base: 10, offset: 3) + let index4 = CountingIndex(base: 8, offset: 5) + + #expect(index1 == index2) + #expect(index1 != index3) + #expect(index1 != index4) + } + + @Test + func comparison() { + let index1 = CountingIndex(base: 5, offset: 10) + let index2 = CountingIndex(base: 8, offset: 2) + let index3 = CountingIndex(base: 5, offset: 15) + + #expect(index1 < index2) + #expect(index1 != index3) + } + + @Test + func description() { + let index1 = CountingIndex(base: 42, offset: 7) + let index2 = CountingIndex(base: 42, offset: nil) + + #expect(index1.description == "(base: 42 | offset: 7)") + #expect(index2.description == "(base: 42 | offset: nil)") + } + + @Test + func nilOffset() { + let index = CountingIndex(base: "test", offset: nil) + + #expect(index.base == "test") + #expect(index.offset == nil) + #expect(index.description.contains("nil")) + } +} + +// MARK: - Cache3Tests + +struct Cache3Tests { + @Test + func put() { + var cache: Cache3 = Cache3() + cache.put(1, value: "1") + #expect(cache.find(1) == "1") + #expect(cache.find(2) == nil) + #expect(cache.find(3) == nil) + #expect(cache.find(4) == nil) + + cache.put(2, value: "2") + #expect(cache.find(1) == "1") + #expect(cache.find(2) == "2") + #expect(cache.find(3) == nil) + #expect(cache.find(4) == nil) + + cache.put(3, value: "3") + #expect(cache.find(1) == "1") + #expect(cache.find(2) == "2") + #expect(cache.find(3) == "3") + #expect(cache.find(4) == nil) + + cache.put(4, value: "4") + #expect(cache.find(1) == nil) + #expect(cache.find(2) == "2") + #expect(cache.find(3) == "3") + #expect(cache.find(4) == "4") + } + + @Test + func get() { + var cache: Cache3 = Cache3() + + let value4 = cache.get(4) { "4" } + #expect(value4 == "4") + #expect(cache.find(1) == nil) + #expect(cache.find(2) == nil) + #expect(cache.find(3) == nil) + #expect(cache.find(4) == "4") + + let value3 = cache.get(3) { "3" } + #expect(value3 == "3") + #expect(cache.find(1) == nil) + #expect(cache.find(2) == nil) + #expect(cache.find(3) == "3") + #expect(cache.find(4) == "4") + + let value2 = cache.get(2) { "2" } + #expect(value2 == "2") + #expect(cache.find(1) == nil) + #expect(cache.find(2) == "2") + #expect(cache.find(3) == "3") + #expect(cache.find(4) == "4") + + let value1 = cache.get(1) { "1" } + #expect(value1 == "1") + #expect(cache.find(1) == "1") + #expect(cache.find(2) == "2") + #expect(cache.find(3) == "3") + #expect(cache.find(4) == nil) + } +} + +// MARK: - CollectionOfTwoTests + +struct CollectionOfTwoTests { + @Test + func initialization() { + let collection = CollectionOfTwo("first", "second") + + #expect(collection.elements.0 == "first") + #expect(collection.elements.1 == "second") + } + + @Test + func indices() { + let collection = CollectionOfTwo(10, 20) + + #expect(collection.startIndex == 0) + #expect(collection.endIndex == 2) + #expect(collection.count == 2) + #expect(collection.indices == 0 ..< 2) + } + + @Test + func subscriptGetter() { + let collection = CollectionOfTwo("a", "b") + + #expect(collection[0] == "a") + #expect(collection[1] == "b") + } + + @Test + func subscriptSetter() { + var collection = CollectionOfTwo(1, 2) + + collection[0] = 10 + collection[1] = 20 + + #expect(collection[0] == 10) + #expect(collection[1] == 20) + #expect(collection.elements.0 == 10) + #expect(collection.elements.1 == 20) + } + + @Test + func iteration() { + let collection = CollectionOfTwo("hello", "world") + var result: [String] = [] + + for element in collection { + result.append(element) + } + + #expect(result == ["hello", "world"]) + } + + @Test + func randomAccessCollection() { + let collection = CollectionOfTwo(100, 200) + + #expect(collection.first == 100) + #expect(collection.last == 200) + #expect(collection.isEmpty == false) + } + + @Test + func map() { + let collection = CollectionOfTwo(1, 2) + let mapped = collection.map { $0 * 10 } + + #expect(mapped == [10, 20]) + } + + @Test + func filter() { + let collection = CollectionOfTwo(1, 2) + let filtered = collection.filter { $0 > 1 } + + #expect(filtered == [2]) + } + + @Test + func reduce() { + let collection = CollectionOfTwo(5, 10) + let sum = collection.reduce(0, +) + + #expect(sum == 15) + } + + @Test + func slicing() { + let collection = CollectionOfTwo("a", "b") + let slice = collection[0 ..< 1] + + #expect(Array(slice) == ["a"]) + } + + @Test + func mutatingMethods() { + var collection = CollectionOfTwo(1, 2) + + for i in collection.indices { + collection[i] *= 2 + } + + #expect(collection[0] == 2) + #expect(collection[1] == 4) + } + + @Test + func differentTypes() { + let intCollection = CollectionOfTwo(1, 2) + let stringCollection = CollectionOfTwo("x", "y") + let doubleCollection = CollectionOfTwo(1.5, 2.5) + + #expect(intCollection.count == 2) + #expect(stringCollection.count == 2) + #expect(doubleCollection.count == 2) + + #expect(intCollection[0] == 1) + #expect(stringCollection[1] == "y") + #expect(doubleCollection[0] == 1.5) + } + + @Test + func indexAdvancement() { + let collection = CollectionOfTwo("first", "second") + + let startIndex = collection.startIndex + let nextIndex = collection.index(after: startIndex) + let endIndex = collection.endIndex + + #expect(startIndex == 0) + #expect(nextIndex == 1) + #expect(endIndex == 2) + + let previousIndex = collection.index(before: endIndex) + #expect(previousIndex == 1) + } + + @Test + func contains() { + let collection = CollectionOfTwo("apple", "banana") + + #expect(collection.contains("apple")) + #expect(collection.contains("banana")) + #expect(!collection.contains("orange")) + } +}