From b3b26374c0b8672a584353a2a9dada8d948cb821 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 12:56:07 +0800 Subject: [PATCH 01/14] Update folder structure for Util --- .../Util => Util/Data}/AnyHashable2.swift | 0 .../{Data/Util => Util/Data}/BitVector.swift | 0 .../Util => Util/Data}/BitVector64.swift | 0 .../Util => Util/Data}/BloomFilter.swift | 0 .../{Data/Util => Util/Data}/Box.swift | 0 .../{Data/Util => Util/Data}/Cache3.swift | 0 .../Data}/ConcatenatedCollection.swift | 0 .../Util => Util/Data}/InlineArray.swift | 0 .../Util => Util/Data}/ObjectCache.swift | 0 .../{Data/Util => Util/Data}/Stack.swift | 0 .../{Data/Util => Util/Data}/UniqueID.swift | 0 .../Util => Util/Data}/VersionSeed.swift | 0 .../Data}/VersionSeedTracker.swift | 0 .../CGAffineTransform+Extension.swift | 0 .../Extension/CGPoint+Extension.swift | 0 .../{ => Util}/Extension/CGPoint+Math.swift | 0 .../Extension/CGRect+Extension.swift | 0 .../Extension/CGSize+Extension.swift | 0 .../{ => Util}/Extension/CGSize+Math.swift | 0 .../Extension/Collection+Extension.swift | 0 .../Extension/Comparable+Extension.swift | 0 .../Util/Extension/Date+Extension.swift | 19 ++++ .../Extension/FloatingPoint+Extension.swift | 90 ++++++++++++++++++- .../Util/Extension/Integer+Extension.swift | 21 +++++ .../Extension/OptionSet+Extension.swift | 0 .../Util/Extension/Optional+Extension.swift | 13 +++ .../Extension/Round+Extension.swift | 0 .../Extension/Unmanaged+Extension.swift | 0 .../Extension/UnsafePointer+Extension.swift | 0 .../Util/{Utils.swift => UIUtils.swift} | 10 +-- .../Data}/AnyHashable2Tests.swift | 0 .../Util => Util/Data}/BitVector64Tests.swift | 0 .../Util => Util/Data}/BitVectorTests.swift | 0 .../Util => Util/Data}/BloomFilterTests.swift | 0 .../{Data/Util => Util/Data}/BoxTests.swift | 0 .../Util => Util/Data}/Cache3Tests.swift | 0 .../Data}/ConcatenatedCollectionTests.swift | 0 .../Util => Util/Data}/InlineArrayTests.swift | 0 .../Util => Util/Data}/ObjectCacheTests.swift | 0 .../{Data/Util => Util/Data}/StackTests.swift | 0 .../Util => Util/Data}/UniqueIDTests.swift | 0 .../Util => Util/Data}/VersionSeedTests.swift | 0 .../CGAffineTransform+ExtensionTests.swift | 0 .../Extension/CGRect+ExtensionTests.swift | 0 .../FloatingPoint+ExtensionTests.swift | 0 45 files changed, 143 insertions(+), 10 deletions(-) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/AnyHashable2.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/BitVector.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/BitVector64.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/BloomFilter.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/Box.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/Cache3.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/ConcatenatedCollection.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/InlineArray.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/ObjectCache.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/Stack.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/UniqueID.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/VersionSeed.swift (100%) rename Sources/OpenSwiftUICore/{Data/Util => Util/Data}/VersionSeedTracker.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/CGAffineTransform+Extension.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/CGPoint+Extension.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/CGPoint+Math.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/CGRect+Extension.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/CGSize+Extension.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/CGSize+Math.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/Collection+Extension.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/Comparable+Extension.swift (100%) create mode 100644 Sources/OpenSwiftUICore/Util/Extension/Date+Extension.swift rename Sources/OpenSwiftUICore/{ => Util}/Extension/FloatingPoint+Extension.swift (71%) create mode 100644 Sources/OpenSwiftUICore/Util/Extension/Integer+Extension.swift rename Sources/OpenSwiftUICore/{ => Util}/Extension/OptionSet+Extension.swift (100%) create mode 100644 Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift rename Sources/OpenSwiftUICore/{ => Util}/Extension/Round+Extension.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/Unmanaged+Extension.swift (100%) rename Sources/OpenSwiftUICore/{ => Util}/Extension/UnsafePointer+Extension.swift (100%) rename Sources/OpenSwiftUICore/Util/{Utils.swift => UIUtils.swift} (81%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/AnyHashable2Tests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/BitVector64Tests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/BitVectorTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/BloomFilterTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/BoxTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/Cache3Tests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/ConcatenatedCollectionTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/InlineArrayTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/ObjectCacheTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/StackTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/UniqueIDTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{Data/Util => Util/Data}/VersionSeedTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{ => Util}/Extension/CGAffineTransform+ExtensionTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{ => Util}/Extension/CGRect+ExtensionTests.swift (100%) rename Tests/OpenSwiftUICoreTests/{ => Util}/Extension/FloatingPoint+ExtensionTests.swift (100%) 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/Cache3.swift b/Sources/OpenSwiftUICore/Util/Data/Cache3.swift similarity index 100% rename from Sources/OpenSwiftUICore/Data/Util/Cache3.swift rename to Sources/OpenSwiftUICore/Util/Data/Cache3.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 100% rename from Sources/OpenSwiftUICore/Extension/Collection+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift 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/Util/Extension/Date+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Date+Extension.swift new file mode 100644 index 000000000..f3169d679 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Extension/Date+Extension.swift @@ -0,0 +1,19 @@ +// +// Date+Extension.swift +// OpenSwiftUICore +// +// Status: Complete + +package import Foundation + +// MARK: - Date + Extension [6.5.4] + +extension Date { + package var nextUp: Date { + Date(timeIntervalSinceReferenceDate: timeIntervalSinceReferenceDate.nextUp) + } + + package var nextDown: Date { + Date(timeIntervalSinceReferenceDate: timeIntervalSinceReferenceDate.nextDown) + } +} diff --git a/Sources/OpenSwiftUICore/Extension/FloatingPoint+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift similarity index 71% rename from Sources/OpenSwiftUICore/Extension/FloatingPoint+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift index 30205062d..4807cd9d4 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. /// @@ -106,3 +109,88 @@ extension FloatingPoint { return rescaledValue.isAlmostEqual(to: otherRescaledValue, tolerance: tolerance) } } + +// 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 + } +} + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#else +#error("Unsupported Platform") +#endif + +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: - 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 + } +} + +package func abs(_ duration: Duration) -> Duration { + (duration < .zero) ? (.zero - duration) : duration +} + diff --git a/Sources/OpenSwiftUICore/Util/Extension/Integer+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Integer+Extension.swift new file mode 100644 index 000000000..933edac9d --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Extension/Integer+Extension.swift @@ -0,0 +1,21 @@ +// +// Integer+Extension.swift +// OpenSwiftUICore +// +// Status: Complete + +// 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))) + } +} 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/Util/Extension/Optional+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift new file mode 100644 index 000000000..78aae9bd8 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift @@ -0,0 +1,13 @@ +// +// Optional+Extension.swift +// OpenSwiftUICore +// +// Status: Complete + +@inlinable +@inline(__always) +func asOptional(_ value: Value) -> Value? { + func unwrap() -> T { value as! T } + let optionalValue: Value? = unwrap() + return optionalValue +} 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 100% rename from Sources/OpenSwiftUICore/Extension/UnsafePointer+Extension.swift rename to Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift 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/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/Data/Util/Cache3Tests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift similarity index 100% rename from Tests/OpenSwiftUICoreTests/Data/Util/Cache3Tests.swift rename to Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift 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/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 From 5c368c07769d03afdcdd6a25daa1f6065f9f1396 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 16:26:10 +0800 Subject: [PATCH 02/14] Add CollectionOfTwo implementation and tests --- .../Util/Data/CollectionOfTwo.swift | 36 +++++ .../Util/Data/CollectionOfTwoTests.swift | 151 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Util/Data/CollectionOfTwo.swift create mode 100644 Tests/OpenSwiftUICoreTests/Util/Data/CollectionOfTwoTests.swift diff --git a/Sources/OpenSwiftUICore/Util/Data/CollectionOfTwo.swift b/Sources/OpenSwiftUICore/Util/Data/CollectionOfTwo.swift new file mode 100644 index 000000000..f9b11c3e2 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Data/CollectionOfTwo.swift @@ -0,0 +1,36 @@ +// +// CollectionOfTwo.swift +// OpenSwiftUICore +// +// Status: Complete + +// 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") + } + } + } +} diff --git a/Tests/OpenSwiftUICoreTests/Util/Data/CollectionOfTwoTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/CollectionOfTwoTests.swift new file mode 100644 index 000000000..0e68e80cc --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/Data/CollectionOfTwoTests.swift @@ -0,0 +1,151 @@ +import Testing +@testable import OpenSwiftUICore + +// MARK: - CollectionOfTwo Tests + +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")) + } +} From bc7e4ea332028413d71d49eb09994db42c4db0a7 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 16:25:59 +0800 Subject: [PATCH 03/14] Add EquatableOptionalObject utility --- .../Util/Data/EquatableOptionalObject.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Util/Data/EquatableOptionalObject.swift diff --git a/Sources/OpenSwiftUICore/Util/Data/EquatableOptionalObject.swift b/Sources/OpenSwiftUICore/Util/Data/EquatableOptionalObject.swift new file mode 100644 index 000000000..d194f3858 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Data/EquatableOptionalObject.swift @@ -0,0 +1,20 @@ +// +// EquatableOptionalObject.swift +// OpenSwiftUICore +// +// Status: Complete + +// 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 + } +} From ee4a87e40e0660118e3c534983a1c4245ef6f7b4 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 16:15:00 +0800 Subject: [PATCH 04/14] Add IndirectOptional --- .../Util/Data/IndirectOptional.swift | 40 +++++++++++++++++++ .../Util/Extension/Optional+Extension.swift | 12 +++--- 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 Sources/OpenSwiftUICore/Util/Data/IndirectOptional.swift diff --git a/Sources/OpenSwiftUICore/Util/Data/IndirectOptional.swift b/Sources/OpenSwiftUICore/Util/Data/IndirectOptional.swift new file mode 100644 index 000000000..b863377ab --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Data/IndirectOptional.swift @@ -0,0 +1,40 @@ +// +// IndirectOptional.swift +// OpenSwiftUICore +// +// Status: Complete + +// 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 {} diff --git a/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift index 78aae9bd8..f25b1518b 100644 --- a/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift @@ -4,10 +4,10 @@ // // Status: Complete -@inlinable -@inline(__always) -func asOptional(_ value: Value) -> Value? { - func unwrap() -> T { value as! T } - let optionalValue: Value? = unwrap() - return optionalValue +// MARK: - Optional + if-then + +extension Optional { + package init(if condition: Bool, then value: @autoclosure () -> Wrapped) { + self = condition ? value() : nil + } } From 7345e95a9577aad6cb96488820dbcee417c575f1 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 16:14:51 +0800 Subject: [PATCH 05/14] Add Comparable extension --- .../Util/Extension/Comparable+Extension.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Sources/OpenSwiftUICore/Util/Extension/Comparable+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Comparable+Extension.swift index 0f68dc8e0..202e9771c 100644 --- a/Sources/OpenSwiftUICore/Util/Extension/Comparable+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/Comparable+Extension.swift @@ -31,3 +31,20 @@ extension Comparable { return result } } + +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 +} + +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) +} From c08b444904ec320c966102aa6c5f22b2e76864e8 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 14:00:04 +0800 Subject: [PATCH 06/14] Add Pair and ArrayID --- .../OpenSwiftUICore/Util/Data/ArrayID.swift | 15 +++++ Sources/OpenSwiftUICore/Util/Data/Pair.swift | 56 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Util/Data/ArrayID.swift create mode 100644 Sources/OpenSwiftUICore/Util/Data/Pair.swift diff --git a/Sources/OpenSwiftUICore/Util/Data/ArrayID.swift b/Sources/OpenSwiftUICore/Util/Data/ArrayID.swift new file mode 100644 index 000000000..4767cd7c9 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Data/ArrayID.swift @@ -0,0 +1,15 @@ +// +// ArrayID.swift +// OpenSwiftUICore +// +// Status: Complete + +// MARK: - ArrayID [6.5.4] + +package struct ArrayID: Hashable { + private let objectIdentifier: ObjectIdentifier + + package init(_ items: [T]) { + self.objectIdentifier = ObjectIdentifier(items as AnyObject) + } +} diff --git a/Sources/OpenSwiftUICore/Util/Data/Pair.swift b/Sources/OpenSwiftUICore/Util/Data/Pair.swift new file mode 100644 index 000000000..0aa9f6fed --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Data/Pair.swift @@ -0,0 +1,56 @@ +// +// Pair.swift +// OpenSwiftUICore +// +// Status: Complete +// ID: DE8DAFA613257BEA44770487175C185C (SwiftUICore?) + +// 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) + } +} From c84af2e36a4e945a1408206359740ceeaba251e5 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 14:00:34 +0800 Subject: [PATCH 07/14] Add UnsafeMutableBufferProjectionPointer --- ...UnsafeMutableBufferProjectionPointer.swift | 58 ++++++++++ .../Extension/UnsafePointer+Extension.swift | 4 + ...eMutableBufferProjectionPointerTests.swift | 106 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Util/Data/UnsafeMutableBufferProjectionPointer.swift create mode 100644 Tests/OpenSwiftUICoreTests/Util/Data/UnsafeMutableBufferProjectionPointerTests.swift diff --git a/Sources/OpenSwiftUICore/Util/Data/UnsafeMutableBufferProjectionPointer.swift b/Sources/OpenSwiftUICore/Util/Data/UnsafeMutableBufferProjectionPointer.swift new file mode 100644 index 000000000..bc22b101e --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Data/UnsafeMutableBufferProjectionPointer.swift @@ -0,0 +1,58 @@ +// +// UnsafeMutableBufferProjectionPointer.swift +// OpenSwiftUICore +// +// Status: Complete + +// 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 +} diff --git a/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift index 91290cf53..c51b6584e 100644 --- a/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift @@ -47,3 +47,7 @@ extension UnsafeMutableBufferPointer { baseAddress ?? .null } } + +package func address(of object: AnyObject) -> UnsafeRawPointer { + unsafeBitCast(object, to: UnsafeRawPointer.self) +} diff --git a/Tests/OpenSwiftUICoreTests/Util/Data/UnsafeMutableBufferProjectionPointerTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/UnsafeMutableBufferProjectionPointerTests.swift new file mode 100644 index 000000000..28d9ee3d7 --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/Data/UnsafeMutableBufferProjectionPointerTests.swift @@ -0,0 +1,106 @@ +// +// ObjectCacheTests.swift +// OpenSwiftUICoreTests + +import Testing +import OpenSwiftUICore + +// MARK: - Test Types + +private struct TestScene { + var x: Int + var y: Double + var z: String +} + +// MARK: - UnsafeMutableBufferProjectionPointerTests + +struct UnsafeMutableBufferProjectionPointerTests { + + @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 mutableAccess() { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 1) + defer { buffer.deallocate() } + + buffer[0] = TestScene(x: 42, y: 3.14, z: "test") + + let xProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.x) + + xProjection[0] = 999 + + #expect(buffer[0].x == 999) + #expect(xProjection[0] == 999) + + let yProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.y) + #expect(yProjection[0] == 3.14) + + let zProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.z) + #expect(zProjection[0] == "test") + } + + + @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) + } +} From 85d8595d1958f5424bfb019eedb8d6069204023b Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 15:32:46 +0800 Subject: [PATCH 08/14] Add Numeric and Sequence extensions --- .../Util/Extension/Numeric+Extension.swift | 17 +++++++++++++++++ .../Util/Extension/Sequence+Extension.swift | 13 +++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Util/Extension/Numeric+Extension.swift create mode 100644 Sources/OpenSwiftUICore/Util/Extension/Sequence+Extension.swift diff --git a/Sources/OpenSwiftUICore/Util/Extension/Numeric+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Numeric+Extension.swift new file mode 100644 index 000000000..55a5a3128 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Extension/Numeric+Extension.swift @@ -0,0 +1,17 @@ +// +// Numeric+Extension.swift +// OpenSwiftUICore +// +// Status: Complete + +// MARK: - Numeric Extension [6.5.4] + +extension Numeric { + package var isNaN: Bool { + self != self + } + + package var isFinite: Bool { + (self - self) == 0 + } +} diff --git a/Sources/OpenSwiftUICore/Util/Extension/Sequence+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Sequence+Extension.swift new file mode 100644 index 000000000..17af98a0b --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Extension/Sequence+Extension.swift @@ -0,0 +1,13 @@ +// +// Sequence+Extension.swift +// OpenSwiftUICore +// +// Status: Complete + +// MARK: - Sequence Extension [6.5.4] + +extension Sequence { + package func first(ofType: T.Type) -> T? { + first { $0 is T } as? T + } +} From cf402b9fe458549270410f7c3c4e39cb0c131ffb Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 15:37:12 +0800 Subject: [PATCH 09/14] Add Collection Extension and tests --- .../Util/Extension/Collection+Extension.swift | 39 +++- .../Extension/Collection+ExtensionTests.swift | 190 ++++++++++++++++++ 2 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 Tests/OpenSwiftUICoreTests/Util/Extension/Collection+ExtensionTests.swift diff --git a/Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift index 0b7a0dca1..52e37c6db 100644 --- a/Sources/OpenSwiftUICore/Util/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) @@ -29,3 +30,39 @@ extension Collection { try withContiguousStorageIfAvailable(body) ?? ContiguousArray(self).withUnsafeBufferPointer(body) } } + +// 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..(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.. Date: Sat, 28 Jun 2025 16:05:37 +0800 Subject: [PATCH 10/14] Add CountingIndexCollection and CountingIndex --- .../Util/Data/CountingIndexCollection.swift | 93 ++++++++++ .../Data/CountingIndexCollectionTests.swift | 175 ++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Util/Data/CountingIndexCollection.swift create mode 100644 Tests/OpenSwiftUICoreTests/Util/Data/CountingIndexCollectionTests.swift diff --git a/Sources/OpenSwiftUICore/Util/Data/CountingIndexCollection.swift b/Sources/OpenSwiftUICore/Util/Data/CountingIndexCollection.swift new file mode 100644 index 000000000..cb27aa12b --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Data/CountingIndexCollection.swift @@ -0,0 +1,93 @@ +// +// CountingIndexCollection.swift +// OpenSwiftUICore +// +// Status: Complete + +// 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"))" + } +} diff --git a/Tests/OpenSwiftUICoreTests/Util/Data/CountingIndexCollectionTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/CountingIndexCollectionTests.swift new file mode 100644 index 000000000..813ec964a --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/Data/CountingIndexCollectionTests.swift @@ -0,0 +1,175 @@ +// +// ConcatenatedCollectionTests.swift +// OpenSwiftUICoreTests +// +// Status: Created by GitHub Copilot + +import Testing +@testable import OpenSwiftUICore + +// MARK: - CountingIndexCollection Tests + +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: - CountingIndex Tests + +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")) + } +} From bc90c052b9b9843af0e31e7d8e1add9e11191c3a Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 16:41:01 +0800 Subject: [PATCH 11/14] Add StandardLibraryAdditionsTests.swift --- .../OpenSwiftUICore/Util/Data/ArrayID.swift | 15 - .../OpenSwiftUICore/Util/Data/Cache3.swift | 80 --- .../Util/Data/CollectionOfTwo.swift | 36 -- .../Util/Data/CountingIndexCollection.swift | 93 --- .../Util/Data/EquatableOptionalObject.swift | 20 - .../Util/Data/IndirectOptional.swift | 40 -- Sources/OpenSwiftUICore/Util/Data/Pair.swift | 56 -- ...UnsafeMutableBufferProjectionPointer.swift | 58 -- .../Util/Extension/Collection+Extension.swift | 36 -- .../Util/Extension/Comparable+Extension.swift | 17 - .../Util/Extension/Date+Extension.swift | 19 - .../Extension/FloatingPoint+Extension.swift | 85 --- .../Util/Extension/Integer+Extension.swift | 21 - .../Util/Extension/Numeric+Extension.swift | 17 - .../Util/Extension/Optional+Extension.swift | 13 - .../Util/Extension/Sequence+Extension.swift | 13 - .../Extension/UnsafePointer+Extension.swift | 3 - .../Util/StandardLibraryAdditions.swift | 580 ++++++++++++++++++ .../Util/Data/Cache3Tests.swift | 62 -- .../Util/Data/CollectionOfTwoTests.swift | 151 ----- .../Data/CountingIndexCollectionTests.swift | 175 ------ ...eMutableBufferProjectionPointerTests.swift | 106 ---- .../Util/StandardLibraryAdditionsTests.swift | 480 +++++++++++++++ 23 files changed, 1060 insertions(+), 1116 deletions(-) delete mode 100644 Sources/OpenSwiftUICore/Util/Data/ArrayID.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Data/Cache3.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Data/CollectionOfTwo.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Data/CountingIndexCollection.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Data/EquatableOptionalObject.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Data/IndirectOptional.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Data/Pair.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Data/UnsafeMutableBufferProjectionPointer.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Extension/Date+Extension.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Extension/Integer+Extension.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Extension/Numeric+Extension.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift delete mode 100644 Sources/OpenSwiftUICore/Util/Extension/Sequence+Extension.swift create mode 100644 Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift delete mode 100644 Tests/OpenSwiftUICoreTests/Util/Data/CollectionOfTwoTests.swift delete mode 100644 Tests/OpenSwiftUICoreTests/Util/Data/CountingIndexCollectionTests.swift delete mode 100644 Tests/OpenSwiftUICoreTests/Util/Data/UnsafeMutableBufferProjectionPointerTests.swift create mode 100644 Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift diff --git a/Sources/OpenSwiftUICore/Util/Data/ArrayID.swift b/Sources/OpenSwiftUICore/Util/Data/ArrayID.swift deleted file mode 100644 index 4767cd7c9..000000000 --- a/Sources/OpenSwiftUICore/Util/Data/ArrayID.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// ArrayID.swift -// OpenSwiftUICore -// -// Status: Complete - -// MARK: - ArrayID [6.5.4] - -package struct ArrayID: Hashable { - private let objectIdentifier: ObjectIdentifier - - package init(_ items: [T]) { - self.objectIdentifier = ObjectIdentifier(items as AnyObject) - } -} diff --git a/Sources/OpenSwiftUICore/Util/Data/Cache3.swift b/Sources/OpenSwiftUICore/Util/Data/Cache3.swift deleted file mode 100644 index ec7e78096..000000000 --- a/Sources/OpenSwiftUICore/Util/Data/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/Util/Data/CollectionOfTwo.swift b/Sources/OpenSwiftUICore/Util/Data/CollectionOfTwo.swift deleted file mode 100644 index f9b11c3e2..000000000 --- a/Sources/OpenSwiftUICore/Util/Data/CollectionOfTwo.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// CollectionOfTwo.swift -// OpenSwiftUICore -// -// Status: Complete - -// 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") - } - } - } -} diff --git a/Sources/OpenSwiftUICore/Util/Data/CountingIndexCollection.swift b/Sources/OpenSwiftUICore/Util/Data/CountingIndexCollection.swift deleted file mode 100644 index cb27aa12b..000000000 --- a/Sources/OpenSwiftUICore/Util/Data/CountingIndexCollection.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// CountingIndexCollection.swift -// OpenSwiftUICore -// -// Status: Complete - -// 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"))" - } -} diff --git a/Sources/OpenSwiftUICore/Util/Data/EquatableOptionalObject.swift b/Sources/OpenSwiftUICore/Util/Data/EquatableOptionalObject.swift deleted file mode 100644 index d194f3858..000000000 --- a/Sources/OpenSwiftUICore/Util/Data/EquatableOptionalObject.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// EquatableOptionalObject.swift -// OpenSwiftUICore -// -// Status: Complete - -// 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/Data/IndirectOptional.swift b/Sources/OpenSwiftUICore/Util/Data/IndirectOptional.swift deleted file mode 100644 index b863377ab..000000000 --- a/Sources/OpenSwiftUICore/Util/Data/IndirectOptional.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// IndirectOptional.swift -// OpenSwiftUICore -// -// Status: Complete - -// 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 {} diff --git a/Sources/OpenSwiftUICore/Util/Data/Pair.swift b/Sources/OpenSwiftUICore/Util/Data/Pair.swift deleted file mode 100644 index 0aa9f6fed..000000000 --- a/Sources/OpenSwiftUICore/Util/Data/Pair.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Pair.swift -// OpenSwiftUICore -// -// Status: Complete -// ID: DE8DAFA613257BEA44770487175C185C (SwiftUICore?) - -// 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) - } -} diff --git a/Sources/OpenSwiftUICore/Util/Data/UnsafeMutableBufferProjectionPointer.swift b/Sources/OpenSwiftUICore/Util/Data/UnsafeMutableBufferProjectionPointer.swift deleted file mode 100644 index bc22b101e..000000000 --- a/Sources/OpenSwiftUICore/Util/Data/UnsafeMutableBufferProjectionPointer.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// UnsafeMutableBufferProjectionPointer.swift -// OpenSwiftUICore -// -// Status: Complete - -// 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 -} diff --git a/Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift index 52e37c6db..d8e80f886 100644 --- a/Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/Collection+Extension.swift @@ -30,39 +30,3 @@ extension Collection { try withContiguousStorageIfAvailable(body) ?? ContiguousArray(self).withUnsafeBufferPointer(body) } } - -// 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..(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..( - 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 -} - -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) -} diff --git a/Sources/OpenSwiftUICore/Util/Extension/Date+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Date+Extension.swift deleted file mode 100644 index f3169d679..000000000 --- a/Sources/OpenSwiftUICore/Util/Extension/Date+Extension.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Date+Extension.swift -// OpenSwiftUICore -// -// Status: Complete - -package import Foundation - -// MARK: - Date + Extension [6.5.4] - -extension Date { - package var nextUp: Date { - Date(timeIntervalSinceReferenceDate: timeIntervalSinceReferenceDate.nextUp) - } - - package var nextDown: Date { - Date(timeIntervalSinceReferenceDate: timeIntervalSinceReferenceDate.nextDown) - } -} diff --git a/Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift index 4807cd9d4..6acc68437 100644 --- a/Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/FloatingPoint+Extension.swift @@ -109,88 +109,3 @@ extension FloatingPoint { return rescaledValue.isAlmostEqual(to: otherRescaledValue, tolerance: tolerance) } } - -// 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 - } -} - -#if canImport(Darwin) -import Darwin -#elseif canImport(Glibc) -import Glibc -#else -#error("Unsupported Platform") -#endif - -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: - 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 - } -} - -package func abs(_ duration: Duration) -> Duration { - (duration < .zero) ? (.zero - duration) : duration -} - diff --git a/Sources/OpenSwiftUICore/Util/Extension/Integer+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Integer+Extension.swift deleted file mode 100644 index 933edac9d..000000000 --- a/Sources/OpenSwiftUICore/Util/Extension/Integer+Extension.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// Integer+Extension.swift -// OpenSwiftUICore -// -// Status: Complete - -// 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))) - } -} diff --git a/Sources/OpenSwiftUICore/Util/Extension/Numeric+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Numeric+Extension.swift deleted file mode 100644 index 55a5a3128..000000000 --- a/Sources/OpenSwiftUICore/Util/Extension/Numeric+Extension.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Numeric+Extension.swift -// OpenSwiftUICore -// -// Status: Complete - -// MARK: - Numeric Extension [6.5.4] - -extension Numeric { - package var isNaN: Bool { - self != self - } - - package var isFinite: Bool { - (self - self) == 0 - } -} diff --git a/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift deleted file mode 100644 index f25b1518b..000000000 --- a/Sources/OpenSwiftUICore/Util/Extension/Optional+Extension.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Optional+Extension.swift -// OpenSwiftUICore -// -// Status: Complete - -// MARK: - Optional + if-then - -extension Optional { - package init(if condition: Bool, then value: @autoclosure () -> Wrapped) { - self = condition ? value() : nil - } -} diff --git a/Sources/OpenSwiftUICore/Util/Extension/Sequence+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/Sequence+Extension.swift deleted file mode 100644 index 17af98a0b..000000000 --- a/Sources/OpenSwiftUICore/Util/Extension/Sequence+Extension.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Sequence+Extension.swift -// OpenSwiftUICore -// -// Status: Complete - -// MARK: - Sequence Extension [6.5.4] - -extension Sequence { - package func first(ofType: T.Type) -> T? { - first { $0 is T } as? T - } -} diff --git a/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift b/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift index c51b6584e..50e991d43 100644 --- a/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift +++ b/Sources/OpenSwiftUICore/Util/Extension/UnsafePointer+Extension.swift @@ -48,6 +48,3 @@ extension UnsafeMutableBufferPointer { } } -package func address(of object: AnyObject) -> UnsafeRawPointer { - unsafeBitCast(object, to: UnsafeRawPointer.self) -} diff --git a/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift b/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift new file mode 100644 index 000000000..30b70e9b2 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift @@ -0,0 +1,580 @@ +// +// 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 + +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 + } +} + +// TODO: + +// 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: - 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/Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift index ef7ddd4ea..0d5d4622a 100644 --- a/Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift +++ b/Tests/OpenSwiftUICoreTests/Util/Data/Cache3Tests.swift @@ -5,65 +5,3 @@ @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/Util/Data/CollectionOfTwoTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/CollectionOfTwoTests.swift deleted file mode 100644 index 0e68e80cc..000000000 --- a/Tests/OpenSwiftUICoreTests/Util/Data/CollectionOfTwoTests.swift +++ /dev/null @@ -1,151 +0,0 @@ -import Testing -@testable import OpenSwiftUICore - -// MARK: - CollectionOfTwo Tests - -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")) - } -} diff --git a/Tests/OpenSwiftUICoreTests/Util/Data/CountingIndexCollectionTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/CountingIndexCollectionTests.swift deleted file mode 100644 index 813ec964a..000000000 --- a/Tests/OpenSwiftUICoreTests/Util/Data/CountingIndexCollectionTests.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// ConcatenatedCollectionTests.swift -// OpenSwiftUICoreTests -// -// Status: Created by GitHub Copilot - -import Testing -@testable import OpenSwiftUICore - -// MARK: - CountingIndexCollection Tests - -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: - CountingIndex Tests - -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")) - } -} diff --git a/Tests/OpenSwiftUICoreTests/Util/Data/UnsafeMutableBufferProjectionPointerTests.swift b/Tests/OpenSwiftUICoreTests/Util/Data/UnsafeMutableBufferProjectionPointerTests.swift deleted file mode 100644 index 28d9ee3d7..000000000 --- a/Tests/OpenSwiftUICoreTests/Util/Data/UnsafeMutableBufferProjectionPointerTests.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// ObjectCacheTests.swift -// OpenSwiftUICoreTests - -import Testing -import OpenSwiftUICore - -// MARK: - Test Types - -private struct TestScene { - var x: Int - var y: Double - var z: String -} - -// MARK: - UnsafeMutableBufferProjectionPointerTests - -struct UnsafeMutableBufferProjectionPointerTests { - - @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 mutableAccess() { - let buffer = UnsafeMutableBufferPointer.allocate(capacity: 1) - defer { buffer.deallocate() } - - buffer[0] = TestScene(x: 42, y: 3.14, z: "test") - - let xProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.x) - - xProjection[0] = 999 - - #expect(buffer[0].x == 999) - #expect(xProjection[0] == 999) - - let yProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.y) - #expect(yProjection[0] == 3.14) - - let zProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.z) - #expect(zProjection[0] == "test") - } - - - @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) - } -} diff --git a/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift b/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift new file mode 100644 index 000000000..d2e6b28b9 --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift @@ -0,0 +1,480 @@ +// +// 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 mutableAccess() { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 1) + defer { buffer.deallocate() } + + buffer[0] = TestScene(x: 42, y: 3.14, z: "test") + + let xProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.x) + + xProjection[0] = 999 + + #expect(buffer[0].x == 999) + #expect(xProjection[0] == 999) + + let yProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.y) + #expect(yProjection[0] == 3.14) + + let zProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.z) + #expect(zProjection[0] == "test") + } + + @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")) + } +} From 6680579cf489db412ecff0e615025ba1a2771ff0 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 17:05:41 +0800 Subject: [PATCH 12/14] Add Dictionary and Range extensions --- .../Util/StandardLibraryAdditions.swift | 168 +++++++++++++++++- 1 file changed, 167 insertions(+), 1 deletion(-) diff --git a/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift b/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift index 30b70e9b2..1a2fd97c6 100644 --- a/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift +++ b/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift @@ -531,7 +531,173 @@ package struct Cache3 where Key: Equatable { } } -// TODO: +// 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] From 91fbb78b0c72f4b3d5858643d2bee4f7dbafc02b Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 18:03:03 +0800 Subject: [PATCH 13/14] Add String extension and array extensions --- .../Runtime/TypeConformance.swift | 6 +- .../Util/StandardLibraryAdditions.swift | 99 ++++++++++++++++++- 2 files changed, 99 insertions(+), 6 deletions(-) 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/Util/StandardLibraryAdditions.swift b/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift index 1a2fd97c6..ff2aebf4e 100644 --- a/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift +++ b/Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift @@ -14,7 +14,7 @@ import Glibc #error("Unsupported Platform") #endif -// MARK: - bind +// MARK: - bind [6.5.4] package func bind(_ action: ((T) -> Void)?, _ value: T) -> (() -> Void)? { guard let action else { @@ -730,6 +730,103 @@ package struct CollectionOfTwo: RandomAccessCollection, MutableCollection { } } +// 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 From 964d75fbba0f9fa096e08ad8c79c23daa6effea5 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Jun 2025 18:25:30 +0800 Subject: [PATCH 14/14] Fix test case issue --- .../Util/StandardLibraryAdditionsTests.swift | 25 ++----------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift b/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift index d2e6b28b9..fadebc76b 100644 --- a/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift +++ b/Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift @@ -32,7 +32,7 @@ struct UnsafeMutableBufferProjectionPointerTests { buffer[1] = 20 buffer[2] = 30 - let pointer = UnsafeMutableBufferProjectionPointer(start: buffer.baseAddress!, count: 3) + let pointer = UnsafeMutableBufferProjectionPointer(start: buffer.baseAddress!, count: 3) #expect(pointer.startIndex == 0) #expect(pointer.endIndex == 3) @@ -54,27 +54,6 @@ struct UnsafeMutableBufferProjectionPointerTests { #expect(pointer.isEmpty) } - @Test - func mutableAccess() { - let buffer = UnsafeMutableBufferPointer.allocate(capacity: 1) - defer { buffer.deallocate() } - - buffer[0] = TestScene(x: 42, y: 3.14, z: "test") - - let xProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.x) - - xProjection[0] = 999 - - #expect(buffer[0].x == 999) - #expect(xProjection[0] == 999) - - let yProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.y) - #expect(yProjection[0] == 3.14) - - let zProjection = UnsafeMutableBufferProjectionPointer(buffer, \TestScene.z) - #expect(zProjection[0] == "test") - } - @Test func bufferProjection() { var scenes = [ @@ -244,7 +223,7 @@ struct CountingIndexTests { let index3 = CountingIndex(base: 5, offset: 15) #expect(index1 < index2) - #expect(index1 == index3) + #expect(index1 != index3) } @Test