diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f09c54e0d..d9ed5ce88 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,8 +9,8 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: - linux_exclude_swift_versions: '[{"swift_version": "5.8"}, {"swift_version": "5.9"}]' - windows_exclude_swift_versions: '[{"swift_version": "5.9"}]' + linux_exclude_swift_versions: '[{"swift_version": "5.8"}, {"swift_version": "5.9"}, {"swift_version": "5.10"}]' + windows_exclude_swift_versions: '[{"swift_version": "5.9"}, {"swift_version": "5.10"}]' macos_exclude_xcode_versions: '[{"xcode_version": "16.0"}, {"xcode_version": "16.1"}]' enable_macos_checks: true soundness: diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c1e6ab43..215f14415 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,33 @@ include(CTest) include(GNUInstallDirs) include(SwiftSupport) +set(collections_swift_flags + "-enable-upcoming-feature MemberImportVisibility" + "-enable-experimental-feature BuiltinModule" + "-enable-experimental-feature Lifetimes" + "-enable-experimental-feature InoutLifetimeDependence" +) +foreach(flag ${collections_swift_flags}) + add_compile_options("$<$:SHELL:${flag}>") +endforeach() + +#add_compile_options("$<$:SHELL:${collections_swift_flags}>") + +set(availability_defs + "SwiftStdlib 5.0: macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2" + "SwiftStdlib 5.1: macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0" + "SwiftStdlib 5.6: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4" + "SwiftStdlib 5.8: macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4" + "SwiftStdlib 5.9: macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0" + "SwiftStdlib 5.10: macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4, visionOS 1.1" + "SwiftStdlib 6.0: macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0" + "SwiftStdlib 6.1: macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4" + "SwiftStdlib 6.2: macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0" + ) +foreach(def ${availability_defs}) + add_compile_options("$<$:SHELL:-enable-experimental-feature \"AvailabilityMacro=${def}\">") +endforeach() + add_subdirectory(Sources) # if(BUILD_TESTING) # add_subdirectory(Tests) diff --git a/Package.swift b/Package.swift index d65d8eb12..cb91a8ba9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.10 +// swift-tools-version:6.2 //===----------------------------------------------------------------------===// // // This source file is part of the Swift Collections open source project @@ -12,12 +12,29 @@ import PackageDescription +let _traits: Set = [ + .default( + enabledTraits: [ + //"UnstableContainersPreview" + ]), + .trait( + name: "UnstableContainersPreview", + description: """ + Enables source-unstable components of the ContainersPreview module in + swift-collections. This allows experimental use of the Container + protocols and associated algorithms. + """), +] + // This package recognizes the conditional compilation flags listed below. // To use enable them, uncomment the corresponding lines or define them // from the package manager command line: // // swift build -Xswiftc -DCOLLECTIONS_INTERNAL_CHECKS -var defines: [String] = [ +var defines: [SwiftSetting] = [ + .define( + "COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW", + .when(traits: ["UnstableContainersPreview"])), // Enables internal consistency checks at the end of initializers and // mutating operations. This can have very significant overhead, so enabling @@ -26,7 +43,7 @@ var defines: [String] = [ // This is mostly useful while debugging an issue with the implementation of // the hash table itself. This setting should never be enabled in production // code. -// "COLLECTIONS_INTERNAL_CHECKS", +// .define("COLLECTIONS_INTERNAL_CHECKS"), // Hashing collections provided by this package usually seed their hash // function with the address of the memory location of their storage, @@ -40,10 +57,10 @@ var defines: [String] = [ // This is mostly useful while debugging an issue with the implementation of // the hash table itself. This setting should never be enabled in production // code. -// "COLLECTIONS_DETERMINISTIC_HASHING", +// .define("COLLECTIONS_DETERMINISTIC_HASHING"), // Enables randomized testing of some data structure implementations. - "COLLECTIONS_RANDOMIZED_TESTING", + .define("COLLECTIONS_RANDOMIZED_TESTING"), // Enable this to build the sources as a single, large module. // This removes the distinct modules for each data structure, instead @@ -52,11 +69,45 @@ var defines: [String] = [ // "COLLECTIONS_SINGLE_MODULE", ] -var _settings: [SwiftSetting] = defines.map { .define($0) } -_settings += [ +let availabilityMacros: KeyValuePairs = [ + "SwiftStdlib 5.0": "macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2", + "SwiftStdlib 5.1": "macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0", + "SwiftStdlib 5.6": "macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4", + "SwiftStdlib 5.8": "macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4", + "SwiftStdlib 5.9": "macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0", + "SwiftStdlib 5.10": "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4, visionOS 1.1", + "SwiftStdlib 6.0": "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0", + "SwiftStdlib 6.1": "macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4", + "SwiftStdlib 6.2": "macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0", + // Note: if you touch these, please make sure to also update the similar lists in + // CMakeLists.txt and Xcode/Shared.xcconfig. +] + +let extraSettings: [SwiftSetting] = [ .enableUpcomingFeature("MemberImportVisibility"), +// .strictMemorySafety(), + .enableExperimentalFeature("BuiltinModule"), + .enableExperimentalFeature("Lifetimes"), + .enableExperimentalFeature("InoutLifetimeDependence"), +// .enableExperimentalFeature("SuppressedAssociatedTypes"), +// .enableExperimentalFeature("AddressableParameters"), +// .enableExperimentalFeature("AddressableTypes"), + + // Note: if you touch these, please make sure to also update the similar lists in + // CMakeLists.txt and Xcode/Shared.xcconfig. ] +let _sharedSettings: [SwiftSetting] = ( + defines + + availabilityMacros.map { name, value in + .enableExperimentalFeature("AvailabilityMacro=\(name): \(value)") + } + + extraSettings +) + +let _settings: [SwiftSetting] = _sharedSettings + [] +let _testSettings: [SwiftSetting] = _sharedSettings + [] + struct CustomTarget { enum Kind { case exported @@ -70,6 +121,8 @@ struct CustomTarget { var dependencies: [Target.Dependency] var directory: String var exclude: [String] + var sources: [String]? + var settings: [SwiftSetting] } extension CustomTarget.Kind { @@ -94,14 +147,18 @@ extension CustomTarget { name: String, dependencies: [Target.Dependency] = [], directory: String? = nil, - exclude: [String] = [] + exclude: [String] = [], + sources: [String]? = nil, + settings: [SwiftSetting]? = nil ) -> CustomTarget { CustomTarget( kind: kind, name: name, dependencies: dependencies, directory: directory ?? name, - exclude: exclude) + exclude: exclude, + sources: sources, + settings: settings ?? (kind.isTest ? _testSettings : _settings)) } func toTarget() -> Target { @@ -117,7 +174,8 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, - swiftSettings: _settings, + sources: sources, + swiftSettings: settings, linkerSettings: linkerSettings) case .test: return Target.testTarget( @@ -125,63 +183,17 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, - swiftSettings: _settings, + swiftSettings: settings, linkerSettings: linkerSettings) } } } -extension Array where Element == CustomTarget { - func toMonolithicTarget( - name: String, - linkerSettings: [LinkerSetting] = [] - ) -> Target { - let targets = self.filter { !$0.kind.isTest } - return Target.target( - name: name, - path: "Sources", - exclude: [ - "CMakeLists.txt", - "BitCollections/BitCollections.docc", - "Collections/Collections.docc", - "DequeModule/DequeModule.docc", - "HashTreeCollections/HashTreeCollections.docc", - "HeapModule/HeapModule.docc", - "OrderedCollections/OrderedCollections.docc", - ] + targets.flatMap { t in - t.exclude.map { "\(t.name)/\($0)" } - }, - sources: targets.map { "\($0.directory)" }, - swiftSettings: _settings, - linkerSettings: linkerSettings) - } - - func toMonolithicTestTarget( - name: String, - dependencies: [Target.Dependency] = [], - linkerSettings: [LinkerSetting] = [] - ) -> Target { - let targets = self.filter { $0.kind.isTest } - return Target.testTarget( - name: name, - dependencies: dependencies, - path: "Tests", - exclude: [ - "README.md", - ] + targets.flatMap { t in - t.exclude.map { "\(t.name)/\($0)" } - }, - sources: targets.map { "\($0.name)" }, - swiftSettings: _settings, - linkerSettings: linkerSettings) - } -} - let targets: [CustomTarget] = [ .target( kind: .testSupport, name: "_CollectionsTestSupport", - dependencies: ["InternalCollectionsUtilities"]), + dependencies: ["InternalCollectionsUtilities", "ContainersPreview"]), .target( kind: .test, name: "CollectionsTestSupportTests", @@ -203,6 +215,25 @@ let targets: [CustomTarget] = [ "UnsafeBitSet/_UnsafeBitSet.swift.gyb", "UnsafeBufferPointer+Extras.swift.gyb", "UnsafeMutableBufferPointer+Extras.swift.gyb", + "UnsafeRawBufferPointer+Extras.swift.gyb", + "UnsafeMutableRawBufferPointer+Extras.swift.gyb", + "LifetimeOverride.swift.gyb", + ]), + + .target( + kind: .exported, + name: "ArrayModule", + dependencies: [ + "InternalCollectionsUtilities", + "ContainersPreview", + ], + exclude: ["CMakeLists.txt"] + ), + .target( + kind: .test, + name: "ArrayTests", + dependencies: [ + "ArrayModule", "_CollectionsTestSupport" ]), .target( @@ -217,6 +248,18 @@ let targets: [CustomTarget] = [ "BitCollections", "_CollectionsTestSupport", "OrderedCollections" ]), + .target( + kind: .exported, + name: "ContainersPreview", + dependencies: ["InternalCollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "ContainersTests", + dependencies: [ + "ContainersPreview", "_CollectionsTestSupport" + ]), + .target( kind: .exported, name: "DequeModule", @@ -262,7 +305,9 @@ let targets: [CustomTarget] = [ name: "_RopeModule", dependencies: ["InternalCollectionsUtilities"], directory: "RopeModule", - exclude: ["CMakeLists.txt"]), + exclude: ["CMakeLists.txt"], + // FIXME: _modify accessors in RopeModule seem to be broken in Swift 6 mode + settings: _sharedSettings + [.swiftLanguageMode(.v5)]), .target( kind: .test, name: "RopeModuleTests", @@ -294,28 +339,15 @@ let targets: [CustomTarget] = [ exclude: ["CMakeLists.txt"]) ] -var _products: [Product] = [] -var _targets: [Target] = [] -if defines.contains("COLLECTIONS_SINGLE_MODULE") { - _products = [ - .library(name: "Collections", targets: ["Collections"]), - ] - _targets = [ - targets.toMonolithicTarget(name: "Collections"), - targets.toMonolithicTestTarget( - name: "CollectionsTests", - dependencies: ["Collections"]), - ] -} else { - _products = targets.compactMap { t in - guard t.kind == .exported else { return nil } - return .library(name: t.name, targets: [t.name]) - } - _targets = targets.map { $0.toTarget() } +let _products: [Product] = targets.compactMap { t in + guard t.kind == .exported else { return nil } + return .library(name: t.name, targets: [t.name]) } +let _targets: [Target] = targets.map { $0.toTarget() } let package = Package( name: "swift-collections", products: _products, + traits: _traits, targets: _targets ) diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift new file mode 100644 index 000000000..39977a9a7 --- /dev/null +++ b/Package@swift-6.0.swift @@ -0,0 +1,387 @@ +// swift-tools-version:6.0 +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +// This package recognizes the conditional compilation flags listed below. +// To use enable them, uncomment the corresponding lines or define them +// from the package manager command line: +// +// swift build -Xswiftc -DCOLLECTIONS_INTERNAL_CHECKS +var defines: [String] = [ + + // Enables internal consistency checks at the end of initializers and + // mutating operations. This can have very significant overhead, so enabling + // this setting invalidates all documented performance guarantees. + // + // This is mostly useful while debugging an issue with the implementation of + // the hash table itself. This setting should never be enabled in production + // code. +// "COLLECTIONS_INTERNAL_CHECKS", + + // Hashing collections provided by this package usually seed their hash + // function with the address of the memory location of their storage, + // to prevent some common hash table merge/copy operations from regressing to + // quadratic behavior. This setting turns off this mechanism, seeding + // the hash function with the table's size instead. + // + // When used in conjunction with the SWIFT_DETERMINISTIC_HASHING environment + // variable, this enables reproducible hashing behavior. + // + // This is mostly useful while debugging an issue with the implementation of + // the hash table itself. This setting should never be enabled in production + // code. +// "COLLECTIONS_DETERMINISTIC_HASHING", + + // Enables randomized testing of some data structure implementations. + "COLLECTIONS_RANDOMIZED_TESTING", + + // Enable this to build the sources as a single, large module. + // This removes the distinct modules for each data structure, instead + // putting them all directly into the `Collections` module. + // Note: This is a source-incompatible variation of the default configuration. +// "COLLECTIONS_SINGLE_MODULE", +] + +let availabilityMacros: KeyValuePairs = [ + "SwiftStdlib 5.0": "macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2", + "SwiftStdlib 5.1": "macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0", + "SwiftStdlib 5.6": "macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4", + "SwiftStdlib 5.8": "macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4", + "SwiftStdlib 5.9": "macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0", + "SwiftStdlib 5.10": "macOS 14.4, iOS 17.4, watchOS 10.4, tvOS 17.4, visionOS 1.1", + "SwiftStdlib 6.0": "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0", + "SwiftStdlib 6.1": "macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4", + "SwiftStdlib 6.2": "macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0", + // Note: if you touch these, please make sure to also update the similar lists in + // CMakeLists.txt and Xcode/Shared.xcconfig. +] + +let extraSettings: [SwiftSetting] = [ + .enableUpcomingFeature("MemberImportVisibility"), + .enableExperimentalFeature("BuiltinModule"), +] + +let _sharedSettings: [SwiftSetting] = ( + defines.map { .define($0) } + + availabilityMacros.map { name, value in + .enableExperimentalFeature("AvailabilityMacro=\(name): \(value)") + } + + extraSettings +) + +let _settings: [SwiftSetting] = _sharedSettings + [] +let _testSettings: [SwiftSetting] = _sharedSettings + [] + +struct CustomTarget { + enum Kind { + case exported + case hidden + case test + case testSupport + } + + var kind: Kind + var name: String + var dependencies: [Target.Dependency] + var directory: String + var exclude: [String] + var sources: [String]? + var settings: [SwiftSetting] +} + +extension CustomTarget.Kind { + func path(for name: String) -> String { + switch self { + case .exported, .hidden: return "Sources/\(name)" + case .test, .testSupport: return "Tests/\(name)" + } + } + + var isTest: Bool { + switch self { + case .exported, .hidden: return false + case .test, .testSupport: return true + } + } +} + +extension CustomTarget { + static func target( + kind: Kind, + name: String, + dependencies: [Target.Dependency] = [], + directory: String? = nil, + exclude: [String] = [], + sources: [String]? = nil, + settings: [SwiftSetting]? = nil + ) -> CustomTarget { + CustomTarget( + kind: kind, + name: name, + dependencies: dependencies, + directory: directory ?? name, + exclude: exclude, + sources: sources, + settings: settings ?? (kind.isTest ? _testSettings : _settings)) + } + + func toTarget() -> Target { + var linkerSettings: [LinkerSetting] = [] + if kind == .testSupport { + linkerSettings.append( + .linkedFramework("XCTest", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS]))) + } + switch kind { + case .exported, .hidden, .testSupport: + return Target.target( + name: name, + dependencies: dependencies, + path: kind.path(for: directory), + exclude: exclude, + sources: sources, + swiftSettings: settings, + linkerSettings: linkerSettings) + case .test: + return Target.testTarget( + name: name, + dependencies: dependencies, + path: kind.path(for: directory), + exclude: exclude, + swiftSettings: settings, + linkerSettings: linkerSettings) + } + } +} + +extension Array where Element == CustomTarget { + func toMonolithicTarget( + name: String, + linkerSettings: [LinkerSetting] = [] + ) -> Target { + let targets = self.filter { !$0.kind.isTest } + return Target.target( + name: name, + path: "Sources", + exclude: [ + "CMakeLists.txt", + "BitCollections/BitCollections.docc", + "Collections/Collections.docc", + "DequeModule/DequeModule.docc", + "HashTreeCollections/HashTreeCollections.docc", + "HeapModule/HeapModule.docc", + "OrderedCollections/OrderedCollections.docc", + ] + targets.flatMap { t in + t.exclude.map { "\(t.name)/\($0)" } + }, + sources: targets.map { "\($0.directory)" }, + swiftSettings: _settings, + linkerSettings: linkerSettings) + } + + @MainActor + func toMonolithicTestTarget( + name: String, + dependencies: [Target.Dependency] = [], + linkerSettings: [LinkerSetting] = [] + ) -> Target { + let targets = self.filter { $0.kind.isTest } + return Target.testTarget( + name: name, + dependencies: dependencies, + path: "Tests", + exclude: [ + "README.md", + ] + targets.flatMap { t in + t.exclude.map { "\(t.name)/\($0)" } + }, + sources: targets.map { "\($0.name)" }, + swiftSettings: _testSettings, + linkerSettings: linkerSettings) + } +} + +let targets: [CustomTarget] = [ + .target( + kind: .testSupport, + name: "_CollectionsTestSupport", + dependencies: ["InternalCollectionsUtilities", "ContainersPreview"]), + .target( + kind: .test, + name: "CollectionsTestSupportTests", + dependencies: ["_CollectionsTestSupport"]), + .target( + kind: .hidden, + name: "InternalCollectionsUtilities", + exclude: [ + "CMakeLists.txt", + "Debugging.swift.gyb", + "Descriptions.swift.gyb", + "IntegerTricks/FixedWidthInteger+roundUpToPowerOfTwo.swift.gyb", + "IntegerTricks/Integer rank.swift.gyb", + "IntegerTricks/UInt+first and last set bit.swift.gyb", + "IntegerTricks/UInt+reversed.swift.gyb", + "RandomAccessCollection+Offsets.swift.gyb", + "UnsafeBitSet/_UnsafeBitSet+Index.swift.gyb", + "UnsafeBitSet/_UnsafeBitSet+_Word.swift.gyb", + "UnsafeBitSet/_UnsafeBitSet.swift.gyb", + "UnsafeBufferPointer+Extras.swift.gyb", + "UnsafeMutableBufferPointer+Extras.swift.gyb", + "UnsafeRawBufferPointer+Extras.swift.gyb", + "UnsafeMutableRawBufferPointer+Extras.swift.gyb", + "LifetimeOverride.swift.gyb", + ]), + + .target( + kind: .exported, + name: "ArrayModule", + dependencies: [ + "InternalCollectionsUtilities", + "ContainersPreview", + ], + exclude: ["CMakeLists.txt"] + ), + .target( + kind: .test, + name: "ArrayTests", + dependencies: [ + "ArrayModule", "_CollectionsTestSupport" + ]), + + .target( + kind: .exported, + name: "BitCollections", + dependencies: ["InternalCollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "BitCollectionsTests", + dependencies: [ + "BitCollections", "_CollectionsTestSupport", "OrderedCollections" + ]), + + .target( + kind: .exported, + name: "ContainersPreview", + dependencies: ["InternalCollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "ContainersTests", + dependencies: [ + "ContainersPreview", "_CollectionsTestSupport" + ]), + + .target( + kind: .exported, + name: "DequeModule", + dependencies: ["InternalCollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "DequeTests", + dependencies: ["DequeModule", "_CollectionsTestSupport"]), + + .target( + kind: .exported, + name: "HashTreeCollections", + dependencies: ["InternalCollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "HashTreeCollectionsTests", + dependencies: ["HashTreeCollections", "_CollectionsTestSupport"]), + + .target( + kind: .exported, + name: "HeapModule", + dependencies: ["InternalCollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "HeapTests", + dependencies: ["HeapModule", "_CollectionsTestSupport"]), + + .target( + kind: .exported, + name: "OrderedCollections", + dependencies: ["InternalCollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "OrderedCollectionsTests", + dependencies: ["OrderedCollections", "_CollectionsTestSupport"]), + + .target( + kind: .exported, + name: "_RopeModule", + dependencies: ["InternalCollectionsUtilities"], + directory: "RopeModule", + exclude: ["CMakeLists.txt"], + // FIXME: _modify accessors in RopeModule seem to be broken in Swift 6 mode + settings: _sharedSettings + [.swiftLanguageMode(.v5)]), + .target( + kind: .test, + name: "RopeModuleTests", + dependencies: ["_RopeModule", "_CollectionsTestSupport"]), + + // These aren't ready for production use yet. +// .target( +// kind: .exported, +// name: "SortedCollections", +// dependencies: ["InternalCollectionsUtilities"], +// directory: "SortedCollections"), +// .target( +// kind: .test, +// name: "SortedCollectionsTests", +// dependencies: ["SortedCollections", "_CollectionsTestSupport"]), + + .target( + kind: .exported, + name: "Collections", + dependencies: [ + "BitCollections", + "DequeModule", + "HashTreeCollections", + "HeapModule", + "OrderedCollections", + "_RopeModule", + //"SortedCollections", + ], + exclude: ["CMakeLists.txt"]) +] + +var _products: [Product] = [] +var _targets: [Target] = [] +if defines.contains("COLLECTIONS_SINGLE_MODULE") { + _products = [ + .library(name: "Collections", targets: ["Collections"]), + ] + _targets = [ + targets.toMonolithicTarget(name: "Collections"), + targets.toMonolithicTestTarget( + name: "CollectionsTests", + dependencies: ["Collections"]), + ] +} else { + _products = targets.compactMap { t in + guard t.kind == .exported else { return nil } + return .library(name: t.name, targets: [t.name]) + } + _targets = targets.map { $0.toTarget() } +} + +let package = Package( + name: "swift-collections", + products: _products, + targets: _targets +) diff --git a/Sources/ArrayModule/CMakeLists.txt b/Sources/ArrayModule/CMakeLists.txt new file mode 100644 index 000000000..aa18f99b9 --- /dev/null +++ b/Sources/ArrayModule/CMakeLists.txt @@ -0,0 +1,30 @@ +#[[ +This source file is part of the Swift Collections Open Source Project + +Copyright (c) 2022 - 2025 Apple Inc. and the Swift project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +if(COLLECTIONS_SINGLE_MODULE) + set(module_name ${COLLECTIONS_MODULE_NAME}) +else() + set(module_name ArrayModule) + add_library(ArrayModule + ${COLLECTIONS_ARRAY_SOURCES}) + target_link_libraries(ArrayModule PRIVATE + InternalCollectionsUtilities ContainersPreview) + set_target_properties(ArrayModule PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + + _install_target(ArrayModule) + set_property(GLOBAL APPEND PROPERTY SWIFT_COLLECTIONS_EXPORTS ArrayModule) +endif() + +target_sources(${module_name} PRIVATE + "DynamicArray.swift" + "DynamicArray+Experimental.swift" + "RigidArray.swift" + "RigidArray+Experimental.swift" +) diff --git a/Sources/ArrayModule/DynamicArray+Experimental.swift b/Sources/ArrayModule/DynamicArray+Experimental.swift new file mode 100644 index 000000000..5dcc6a607 --- /dev/null +++ b/Sources/ArrayModule/DynamicArray+Experimental.swift @@ -0,0 +1,365 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +import ContainersPreview +#endif + +#if compiler(>=6.2) && (compiler(>=6.3) || !os(Windows)) // FIXME: [2025-08-17] Windows has no 6.2 snapshot with OutputSpan +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + +#if FIXME +extension DynamicArray /*where Element: Copyable*/ { + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & ~Copyable & ~Escapable>( + capacity: Int? = nil, + copying contents: borrowing C + ) { + self.init(consuming: RigidArray(capacity: capacity, copying: contents)) + } + + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & Sequence>( + capacity: Int? = nil, + copying contents: C + ) { + self.init(consuming: RigidArray(capacity: capacity, copying: contents)) + } +} +#endif + +#if FIXME +extension DynamicArray where Element: ~Copyable { + @inlinable + @inline(__always) + @_lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _storage.borrowElement(at: index) + } +} +#endif + +#if FIXME +extension DynamicArray: RandomAccessContainer where Element: ~Copyable {} +#endif + +#if FIXME +extension DynamicArray where Element: ~Copyable { + @inlinable + @_lifetime(&self) + public mutating func mutateElement(at index: Int) -> Inout { + _storage.mutateElement(at: index) + } +} +#endif + +extension DynamicArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + @_transparent + internal mutating func _edit( + freeCapacity: Int, + inPlaceMutation: (inout OutputSpan) -> R, + reallocatingMutation: (inout InputSpan, inout OutputSpan) -> R + ) -> R { + if _storage.freeCapacity >= freeCapacity { + return edit(inPlaceMutation) + } + let newCapacity = _grow(freeCapacity: freeCapacity) + return _storage.reallocate(capacity: newCapacity, with: reallocatingMutation) + } +} + +#if FIXME +extension DynamicArray where Element: ~Copyable { + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a container that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the array. + /// + /// - Complexity: O(`count`) + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func removeAll( + where shouldBeRemoved: (borrowing Element) throws(E) -> Bool + ) throws(E) { + // FIXME: Remove this in favor of a standard algorithm. + let suffixStart = try _halfStablePartition(isSuffixElement: shouldBeRemoved) + removeSubrange(suffixStart...) + } +} +#endif + +extension DynamicArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public mutating func append( + moving items: inout InputSpan + ) { + _ensureFreeCapacity(items.count) + _storage.append(moving: &items) + } + + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public mutating func append( + moving items: inout OutputSpan + ) { + _ensureFreeCapacity(items.count) + _storage.append(moving: &items) + } +} + +extension DynamicArray { +#if FIXME + public mutating func _appendContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + var i = newElements.startIndex + while true { + let span = newElements.span(after: &i) + if span.isEmpty { break } + self.append(copying: span) + } + } +#endif + +#if FIXME + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`), when amortized over many invocations + /// over the same array. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func append< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + _appendContainer(copying: newElements) + } +#endif + +#if FIXME + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`), when amortized over many invocations + /// over the same array. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func append< + C: Container & Sequence + >( + copying newElements: borrowing C + ) { + _appendContainer(copying: newElements) + } +#endif +} + +#if FIXME +extension DynamicArray { + @available(SwiftStdlib 6.2, *) + @inlinable + internal mutating func _insertContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let newCount = newElements.count + _ensureFreeCapacity(newCount) + _storage._insertContainer( + at: index, copying: newElements, newCount: newCount) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer(copying: newElements, at: index) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & Collection + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer(copying: newElements, at: index) + } + +} +#endif + +#if FIXME +extension DynamicArray { + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func _replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copyingContainer newElements: borrowing C + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let c = newElements.count + _ensureFreeCapacity(c) + _storage._replaceSubrange( + subrange, copyingContainer: newElements, newCount: c) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange(subrange, copyingContainer: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & Collection + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange(subrange, copyingContainer: newElements) + } +} +#endif + +#endif +#endif diff --git a/Sources/ArrayModule/DynamicArray.swift b/Sources/ArrayModule/DynamicArray.swift new file mode 100644 index 000000000..53aa6ac28 --- /dev/null +++ b/Sources/ArrayModule/DynamicArray.swift @@ -0,0 +1,1050 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif + +#if compiler(<6.2) || (compiler(<6.3) && os(Windows)) // FIXME: [2025-08-17] Windows has no 6.2 snapshot with OutputSpan + +/// A dynamically self-resizing, heap allocated, noncopyable array +/// of potentially noncopyable elements. +@frozen +@available(*, unavailable, message: "DynamicArray requires a Swift 6.2 toolchain") +public struct DynamicArray: ~Copyable { + @usableFromInline + internal var _storage: RigidArray + + @inlinable + public init() { + fatalError() + } +} + +#else + +/// A dynamically self-resizing, heap allocated, noncopyable array of +/// potentially noncopyable elements. +/// +/// `DynamicArray` instances automatically resize their underlying storage as +/// needed to accommodate newly inserted items, using a geometric growth curve. +/// This frees code using `DynamicArray` from having to allocate enough +/// capacity in advance; on the other hand, it makes it difficult to tell +/// when and where such reallocations may happen. +/// +/// For example, appending an element to a dynamic array has highly variable +/// complexity; often, it runs at a constant cost, but if the operation has to +/// resize storage, then the cost of an individual append suddenly becomes +/// proportional to the size of the whole array. +/// +/// The geometric growth curve allows the cost of such latency spikes to +/// get amortized across repeated invocations, bringing the average cost back +/// to O(1); but they make this construct less suitable for use cases that +/// expect predictable, consistent performance on every operation. +/// +/// Implicit growth also makes it more difficult to predict/analyze the amount +/// of memory an algorithm would need. Developers targeting environments with +/// stringent limits on heap allocations may prefer to avoid using dynamically +/// resizing array types as a matter of policy. The type `RigidArray` provides +/// a fixed-capacity array variant that caters specifically for these use cases, +/// trading ease-of-use for more consistent/predictable execution. +@frozen +public struct DynamicArray: ~Copyable { + @usableFromInline + internal var _storage: RigidArray + + @inlinable + public init() { + _storage = .init(capacity: 0) + } +} +extension DynamicArray: Sendable where Element: Sendable & ~Copyable {} + +//MARK: - Initializers + +extension DynamicArray where Element: ~Copyable { + @inlinable + public init(capacity: Int) { + _storage = .init(capacity: capacity) + } +} + +extension DynamicArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @inlinable + public init( + capacity: Int, + initializedWith body: (inout OutputSpan) throws(E) -> Void + ) throws(E) { + self.init(capacity: capacity) + try edit(body) + } +} + +extension DynamicArray where Element: ~Copyable { + @inlinable + public init(consuming storage: consuming RigidArray) { + self._storage = storage + } +} + +extension DynamicArray /*where Element: Copyable*/ { + /// Creates a new array containing the specified number of a single, + /// repeated value. + /// + /// - Parameters: + /// - repeatedValue: The element to repeat. + /// - count: The number of times to repeat the value passed in the + /// `repeating` parameter. `count` must be zero or greater. + public init(repeating repeatedValue: Element, count: Int) { + self.init(consuming: RigidArray(repeating: repeatedValue, count: count)) + } +} + +extension DynamicArray /*where Element: Copyable*/ { + @_alwaysEmitIntoClient + @inline(__always) + public init(capacity: Int? = nil, copying contents: some Sequence) { + self.init(capacity: capacity ?? 0) + self.append(copying: contents) + } +} + +//MARK: - Basics + +extension DynamicArray where Element: ~Copyable { + @inlinable + @inline(__always) + public var capacity: Int { _assumeNonNegative(_storage.capacity) } + + @inlinable + @inline(__always) + public var freeCapacity: Int { + _assumeNonNegative(_storage.capacity &- _storage.count) + } +} + +//MARK: - Span creation + +extension DynamicArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + public var span: Span { + @_lifetime(borrow self) + @inlinable + get { + _storage.span + } + } + + @available(SwiftStdlib 5.0, *) + public var mutableSpan: MutableSpan { + @_lifetime(&self) + @inlinable + mutating get { + _storage.mutableSpan + } + } +} + +extension DynamicArray where Element: ~Copyable { + /// Arbitrarily edit the storage underlying this array by invoking a + /// user-supplied closure with a mutable `OutputSpan` view over it. + /// This method calls its function argument precisely once, allowing it to + /// arbitrarily modify the contents of the output span it is given. + /// The argument is free to add, remove or reorder any items; however, + /// it is not allowed to replace the span or change its capacity. + /// + /// When the function argument finishes (whether by returning or throwing an + /// error) the rigid array instance is updated to match the final contents of + /// the output span. + /// + /// - Parameter body: A function that edits the contents of this array through + /// an `OutputSpan` argument. This method invokes this function + /// precisely once. + /// - Returns: This method returns the result of its function argument. + /// - Complexity: Adds O(1) overhead to the complexity of the function + /// argument. + @available(SwiftStdlib 5.0, *) + @inlinable @inline(__always) + public mutating func edit( + _ body: (inout OutputSpan) throws(E) -> R + ) throws(E) -> R { + try _storage.edit(body) + } +} + +//MARK: - Random-access & mutable container primitives + +extension DynamicArray where Element: ~Copyable { + public typealias Index = Int + + @inlinable + @inline(__always) + public var isEmpty: Bool { _storage.isEmpty } + + @inlinable + @inline(__always) + public var count: Int { _storage.count } + + @inlinable + @inline(__always) + public var startIndex: Int { _storage.startIndex } + + @inlinable + @inline(__always) + public var endIndex: Int { _storage.count } + + @inlinable + @inline(__always) + public var indices: Range { _storage.indices } + + @inlinable + public subscript(position: Int) -> Element { + unsafeAddress { + _storage._ptr(to: position) + } + unsafeMutableAddress { + _storage._mutablePtr(to: position) + } + } +} + +extension DynamicArray where Element: ~Copyable { + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + _storage.swapAt(i, j) + } +} + +extension DynamicArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @inlinable + @_lifetime(borrow self) + public func span(after index: inout Int) -> Span { + _storage.span(after: &index) + } + + @available(SwiftStdlib 5.0, *) + @inlinable + @_lifetime(borrow self) + public func span(before index: inout Int) -> Span { + _storage.span(before: &index) + } +} + +extension DynamicArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @_lifetime(&self) + public mutating func mutableSpan( + after index: inout Int + ) -> MutableSpan { + _storage.mutableSpan(after: &index) + } + + @available(SwiftStdlib 5.0, *) + @_lifetime(&self) + public mutating func mutableSpan( + before index: inout Int + ) -> MutableSpan { + _storage.mutableSpan(before: &index) + } +} + +//MARK: - Resizing + +@_alwaysEmitIntoClient +@_transparent +internal func _growDynamicArrayCapacity(_ capacity: Int) -> Int { + // A growth factor of 1.5 seems like a reasonable compromise between + // over-allocating memory and wasting cycles on repeatedly resizing storage. + let c = (3 &* UInt(bitPattern: capacity) &+ 1) / 2 + return Int(bitPattern: c) +} + +extension DynamicArray where Element: ~Copyable { + @inlinable @inline(never) + public mutating func reallocate(capacity: Int) { + _storage.reallocate(capacity: capacity) + } + + @inlinable @inline(never) + public mutating func reserveCapacity(_ n: Int) { + _storage.reserveCapacity(n) + } + + @_alwaysEmitIntoClient + @_transparent + internal mutating func _ensureFreeCapacity(_ freeCapacity: Int) { + guard _storage.freeCapacity < freeCapacity else { return } + _ensureFreeCapacitySlow(freeCapacity) + } + + @_alwaysEmitIntoClient + @_transparent + internal func _grow(freeCapacity: Int) -> Int { + Swift.max( + count + freeCapacity, + _growDynamicArrayCapacity(capacity)) + } + + @inlinable + internal mutating func _ensureFreeCapacitySlow(_ freeCapacity: Int) { + let newCapacity = _grow(freeCapacity: freeCapacity) + reallocate(capacity: newCapacity) + } +} + +//MARK: - Removal operations + +extension DynamicArray where Element: ~Copyable { + /// Removes all elements from the array, optionally preserving its + /// allocated capacity. + /// + /// - Complexity: O(*n*), where *n* is the original count of the array. + @inlinable + @inline(__always) + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { + if keepCapacity { + _storage.removeAll() + } else { + _storage = RigidArray(capacity: 0) + } + } + + /// Removes and returns the last element of the array. + /// + /// The array must not be empty. + /// + /// - Returns: The last element of the original array. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + @discardableResult + public mutating func removeLast() -> Element { + _storage.removeLast() + } + + /// Removes and discards the specified number of elements from the end of the + /// array. + /// + /// Attempting to remove more elements than exist in the array triggers a + /// runtime error. + /// + /// - Parameter k: The number of elements to remove from the array. + /// `k` must be greater than or equal to zero and must not exceed + /// the count of the array. + /// + /// - Complexity: O(`k`) + @inlinable + public mutating func removeLast(_ k: Int) { + _storage.removeLast(k) + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// gap. + /// + /// - Parameter i: The position of the element to remove. `index` must be + /// a valid index of the array that is not equal to the end index. + /// - Returns: The removed element. + /// + /// - Complexity: O(`self.count`) + @inlinable + @inline(__always) + @discardableResult + public mutating func remove(at index: Int) -> Element { + _storage.remove(at: index) + } + + /// Removes the specified subrange of elements from the array. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`self.count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _storage.removeSubrange(bounds) + } + + /// Removes the specified subrange of elements from the array. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`self.count`) + @_alwaysEmitIntoClient + public mutating func removeSubrange(_ bounds: some RangeExpression) { + // FIXME: Remove this in favor of a standard algorithm. + removeSubrange(bounds.relative(to: indices)) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Removes and returns the last element of the array, if there is one. + /// + /// - Returns: The last element of the array if the array is not empty; + /// otherwise, `nil`. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public mutating func popLast() -> Element? { + if isEmpty { return nil } + return removeLast() + } +} + +//MARK: - Append operations + +extension DynamicArray where Element: ~Copyable { + /// Adds an element to the end of the array. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this reallocates the array's storage to grow its capacity. + /// + /// - Parameter item: The element to append to the collection. + /// + /// - Complexity: O(1) when amortized over many invocations on the same array + @inlinable + public mutating func append(_ item: consuming Element) { + _ensureFreeCapacity(1) + _storage.append(item) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Moves the elements of a buffer to the end of this array, leaving the + /// buffer uninitialized. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this reallocates the array's storage to grow its capacity. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) when amortized over many invocations on + /// the same array + @_alwaysEmitIntoClient + public mutating func append( + moving items: UnsafeMutableBufferPointer + ) { + _ensureFreeCapacity(items.count) + _storage.append(moving: items) + } + + /// Appends the elements of a given array to the end of this array by moving + /// them between the containers. On return, the input array becomes empty, but + /// it is not destroyed, and it preserves its original storage capacity. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this automatically grows the target array's + /// capacity. + /// + /// - Parameters + /// - items: An array whose items to move to the end of this array. + /// + /// - Complexity: O(`items.count`) when amortized over many invocations on + /// the same array + @_alwaysEmitIntoClient + public mutating func append( + moving items: inout RigidArray + ) { + // FIXME: Remove this in favor of a generic algorithm over range-replaceable containers + _ensureFreeCapacity(items.count) + _storage.append(moving: &items) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Appends the elements of a given container to the end of this array by + /// consuming the source container. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: An array whose items to move to the end of this array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func append( + consuming items: consuming RigidArray + ) { + // FIXME: Remove this in favor of a generic algorithm over consumable containers + var items = items + self.append(moving: &items) + } +} + +extension DynamicArray { + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @_alwaysEmitIntoClient + public mutating func append( + copying newElements: UnsafeBufferPointer + ) { + _ensureFreeCapacity(newElements.count) + unsafe _storage.append(copying: newElements) + } + + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @_alwaysEmitIntoClient + public mutating func append( + copying newElements: UnsafeMutableBufferPointer + ) { + unsafe self.append(copying: UnsafeBufferPointer(newElements)) + } + + /// Copies the elements of a span to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A span whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func append(copying newElements: Span) { + _ensureFreeCapacity(newElements.count) + _storage.append(copying: newElements) + } + + /// Copies the elements of a sequence to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. This + /// reallocation can happen multiple times. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`, when + /// amortized over many invocations over the same array. + @_alwaysEmitIntoClient + public mutating func append(copying newElements: some Sequence) { + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + _ensureFreeCapacity(buffer.count) + unsafe _storage.append(copying: buffer) + return + } + if done != nil { return } + + _ensureFreeCapacity(newElements.underestimatedCount) + var it = _storage._append(prefixOf: newElements) + while let item = it.next() { + _ensureFreeCapacity(1) + _storage.append(item) + } + } +} + +//MARK: - Insert operations + +extension DynamicArray where Element: ~Copyable { + /// Inserts a new element into the array at the specified position. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this reallocates storage to extend its capacity. + /// + /// The new element is inserted before the element currently at the specified + /// index. If you pass the array's `endIndex` as the `index` parameter, then + /// the new element is appended to the container. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// - Parameter item: The new element to insert into the array. + /// - Parameter i: The position at which to insert the new element. + /// `index` must be a valid index in the array. + /// + /// - Complexity: O(`self.count`) + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count) + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(1) + _storage.insert(item, at: index) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Moves the elements of a fully initialized buffer into this array, + /// starting at the specified position, and leaving the buffer + /// uninitialized. + /// + /// If the array does not have sufficient capacity to hold all elements, + /// then this reallocates storage to extend its capacity. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`self.count` + `items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: UnsafeMutableBufferPointer, + at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(items.count) + _storage.insert(moving: items, at: index) + } + + /// Inserts the elements of a given array into the given position in this + /// array by moving them between the containers. On return, the input array + /// becomes empty, but it is not destroyed, and it preserves its original + /// storage capacity. + /// + /// If the array does not have sufficient capacity to hold all elements, + /// then this reallocates storage to extend its capacity. + /// + /// - Parameters + /// - items: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: inout RigidArray, + at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(items.count) + _storage.insert(moving: &items, at: index) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Inserts the elements of a given array into the given position in this + /// array by consuming the source container. + /// + /// If the array does not have sufficient capacity to hold all elements, + /// then this reallocates storage to extend its capacity. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`self.count` + `items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + consuming items: consuming RigidArray, + at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(items.count) + _storage.insert(consuming: items, at: index) + } +} + +extension DynamicArray { + /// Copyies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + public mutating func insert( + copying newElements: UnsafeBufferPointer, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + unsafe _storage.insert(copying: newElements, at: index) + } + + /// Copyies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + public mutating func insert( + copying newElements: UnsafeMutableBufferPointer, + at index: Int + ) { + unsafe self.insert(copying: UnsafeBufferPointer(newElements), at: index) + } + + /// Copies the elements of a span into this array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func insert( + copying newElements: Span, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + _storage.insert(copying: newElements, at: index) + } + + /// Copies the elements of a collection into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved + /// to make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + public mutating func insert( + copying newElements: some Collection, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let newCount = newElements.count + _ensureFreeCapacity(newCount) + _storage._insertCollection( + at: index, copying: newElements, newCount: newCount) + } +} + +//MARK: - Range replacement + +extension DynamicArray where Element: ~Copyable { + /// Replaces the specified range of elements by moving the elements of a + /// fully initialized buffer into their place. On return, the buffer is left + /// in an uninitialized state. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + moving newElements: UnsafeMutableBufferPointer, + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count - subrange.count) + _storage.replaceSubrange(subrange, moving: newElements) + } + + /// Replaces the specified range of elements by moving the elements of a + /// another array into their place. On return, the source array + /// becomes empty, but it is not destroyed, and it preserves its original + /// storage capacity. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + moving newElements: inout RigidArray, + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count - subrange.count) + _storage.replaceSubrange(subrange, moving: &newElements) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Replaces the specified range of elements by moving the elements of a + /// given array into their place, consuming it in the process. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + consuming newElements: consuming RigidArray, + ) { + replaceSubrange(subrange, moving: &newElements) + } +} + +extension DynamicArray { + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeBufferPointer + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + unsafe _storage.replaceSubrange(subrange, copying: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeMutableBufferPointer + ) { + unsafe self.replaceSubrange( + subrange, copying: UnsafeBufferPointer(newElements)) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given span. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length span as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: Span + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + _storage.replaceSubrange(subrange, copying: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given collection. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length collection as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: __owned some Collection + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let c = newElements.count + _ensureFreeCapacity(c) + _storage._replaceSubrange( + subrange, copyingCollection: newElements, newCount: c) + } +} +#endif diff --git a/Sources/ArrayModule/RigidArray+Experimental.swift b/Sources/ArrayModule/RigidArray+Experimental.swift new file mode 100644 index 000000000..42d0b1745 --- /dev/null +++ b/Sources/ArrayModule/RigidArray+Experimental.swift @@ -0,0 +1,455 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +import ContainersPreview +#endif + +#if compiler(>=6.2) && (compiler(>=6.3) || !os(Windows)) // FIXME: [2025-08-17] Windows has no 6.2 snapshot with OutputSpan +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + +extension RigidArray /*where Element: Copyable*/ { +#if FIXME + @_alwaysEmitIntoClient + @inline(__always) + public init & Sequence>( + capacity: Int, + copying contents: C + ) { + self.init(capacity: capacity) + self.append(copying: contents) + } +#endif + +#if FIXME + @_alwaysEmitIntoClient + @inline(__always) + public init & ~Copyable & ~Escapable>( + capacity: Int? = nil, + copying contents: borrowing C + ) { + self.init(capacity: capacity ?? contents.count) + self.append(copying: contents) + } +#endif + +#if FIXME + @_alwaysEmitIntoClient + @inline(__always) + public init & Collection>( + capacity: Int? = nil, + copying contents: C + ) { + self.init(capacity: capacity ?? contents.count) + self.append(copying: contents) + } +#endif +} + +#if FIXME +extension RigidArray where Element: ~Copyable { + @inlinable + @_lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + precondition(index >= 0 && index < _count, "Index out of bounds") + return unsafe Borrow( + unsafeAddress: _storage.baseAddress.unsafelyUnwrapped.advanced(by: index), + borrowing: self + ) + } +} +#endif + +#if FIXME +extension RigidArray: RandomAccessContainer where Element: ~Copyable { +} +#endif + +#if FIXME +extension RigidArray where Element: ~Copyable { + @inlinable + @_lifetime(&self) + public mutating func mutateElement(at index: Int) -> Inout { + precondition(index >= 0 && index < _count) + return unsafe Inout( + unsafeAddress: _storage.baseAddress.unsafelyUnwrapped.advanced(by: index), + mutating: &self + ) + } +} +#endif + +#if FIXME +extension RigidArray: MutableContainer where Element: ~Copyable { +} +#endif + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @inlinable + public mutating func reallocate( + capacity: Int, + with body: ( + inout InputSpan, + inout OutputSpan + ) throws(E) -> R + ) throws(E) -> R { + var source = InputSpan(buffer: _storage, initializedCount: _count) + let newStorage: UnsafeMutableBufferPointer = .allocate( + capacity: capacity) + var target = OutputSpan(buffer: newStorage, initializedCount: 0) + defer { + _ = consume source + _storage.deallocate() + _count = target.finalize(for: newStorage) + _storage = newStorage + source = .init() + target = .init() + } + return try body(&source, &target) + } +} + +#if FIXME +extension RigidArray where Element: ~Copyable { + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a container that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the array. + /// + /// - Complexity: O(`count`) + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func removeAll( + where shouldBeRemoved: (borrowing Element) throws(E) -> Bool + ) throws(E) { + // FIXME: Remove this in favor of a standard algorithm. + let suffixStart = try _halfStablePartition(isSuffixElement: shouldBeRemoved) + removeSubrange(suffixStart...) + } +} +#endif + +extension RigidArray where Element: ~Copyable { + @_lifetime(&self) + public mutating func popLast(_ count: Int) -> InputSpan { + let c = Swift.min(count, self.count) + self._count &-= c + let span = InputSpan( + buffer: self._storage._extracting(last: c), + initializedCount: c) + return _overrideLifetime(span, mutating: &self) + } +} + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public mutating func append( + moving items: inout InputSpan + ) { + items.withUnsafeMutableBufferPointer { buffer, count in + let source = buffer._extracting(last: count) + unsafe self.append(moving: source) + count = 0 + } + } + + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public mutating func append( + moving items: inout OutputSpan + ) { + items.withUnsafeMutableBufferPointer { buffer, count in + let source = buffer._extracting(first: count) + unsafe self.append(moving: source) + count = 0 + } + } +} + +extension RigidArray { +#if FIXME + @inlinable + internal mutating func _appendContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + let (copied, end) = unsafe _freeSpace._initializePrefix( + copying: newElements) + precondition(end == newElements.endIndex, "RigidArray capacity overflow") + _count += copied + } +#endif + +#if FIXME + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// container, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func append & ~Copyable & ~Escapable>( + copying newElements: borrowing C + ) { + _appendContainer(copying: newElements) + } +#endif + +#if FIXME + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// container, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`. + @_alwaysEmitIntoClient + @inline(__always) + public mutating func append< + C: Container & Sequence + >(copying newElements: C) { + _appendContainer(copying: newElements) + } +#endif +} + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: inout InputSpan, + at index: Int + ) { + items.withUnsafeMutableBufferPointer { buffer, count in + let source = buffer._extracting(last: count) + unsafe self.append(moving: source) + count = 0 + } + } + + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: inout OutputSpan, + at index: Int + ) { + items.withUnsafeMutableBufferPointer { buffer, count in + let source = buffer._extracting(first: count) + unsafe self.append(moving: source) + count = 0 + } + } +} + +extension RigidArray { +#if FIXME + @inlinable + internal mutating func _insertContainer< + C: Container & ~Copyable & ~Escapable + >( + at index: Int, + copying items: borrowing C, + newCount: Int + ) { + precondition(index >= 0 && index <= _count, "Index out of bounds") + precondition(newCount <= freeCapacity, "RigidArray capacity overflow") + let target = unsafe _openGap(at: index, count: newCount) + let (copied, end) = unsafe target._initializePrefix(copying: items) + precondition( + copied == newCount && end == items.endIndex, + "Broken Container: count doesn't match contents") + _count += newCount + } +#endif + +#if FIXME + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer( + at: index, copying: newElements, newCount: newElements.count) + } +#endif + +#if FIXME + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & Collection + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer( + at: index, copying: newElements, newCount: newElements.count) + } +#endif +} + +extension RigidArray { +#if FIXME + @inlinable + public mutating func _replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copyingContainer newElements: borrowing C, + newCount: Int + ) { + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: newCount) + let (copied, end) = unsafe gap._initializePrefix(copying: newElements) + precondition( + copied == newCount && end == newElements.endIndex, + "Broken Container: count doesn't match contents") + } +#endif + +#if FIXME + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange( + subrange, copyingContainer: newElements, newCount: newElements.count) + } +#endif + +#if FIXME + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & Collection + >( + _ subrange: Range, + copying newElements: C + ) { + _replaceSubrange( + subrange, copyingContainer: newElements, newCount: newElements.count) + } +#endif +} + +#endif +#endif diff --git a/Sources/ArrayModule/RigidArray.swift b/Sources/ArrayModule/RigidArray.swift new file mode 100644 index 000000000..e6dbb1bae --- /dev/null +++ b/Sources/ArrayModule/RigidArray.swift @@ -0,0 +1,1363 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif + +#if compiler(<6.2) || (compiler(<6.3) && os(Windows)) // FIXME: [2025-08-17] Windows has no 6.2 snapshot with OutputSpan + +/// A fixed capacity, heap allocated, noncopyable array of potentially +/// noncopyable elements. +@frozen +@available(*, unavailable, message: "RigidArray requires a Swift 6.2 toolchain") +public struct RigidArray: ~Copyable { + @usableFromInline + internal var _storage: UnsafeMutableBufferPointer + + @usableFromInline + internal var _count: Int + + deinit { + _storage.extracting(0 ..< _count).deinitialize() + _storage.deallocate() + } + + public init() { + fatalError() + } +} + +#else + +/// A fixed capacity, heap allocated, noncopyable array of potentially +/// noncopyable elements. +/// +/// `RigidArray` instances are created with a certain maximum capacity. Elements +/// can be added to the array up to that capacity, but no more: trying to add an +/// item to a full array results in a runtime trap. +/// +/// var items = RigidArray(capacity: 2) +/// items.append(1) +/// items.append(2) +/// items.append(3) // Runtime error: RigidArray capacity overflow +/// +/// Rigid arrays provide convenience properties to help verify that it has +/// enough available capacity: `isFull` and `freeCapacity`. +/// +/// guard items.freeCapacity >= 4 else { throw CapacityOverflow() } +/// items.append(copying: newItems) +/// +/// It is possible to extend or shrink the capacity of a rigid array instance, +/// but this needs to be done explicitly, with operations dedicated to this +/// purpose (such as ``reserveCapacity`` and ``reallocate(capacity:)``). +/// The array never resizes itself automatically. +/// +/// It therefore requires careful manual analysis or up front runtime capacity +/// checks to prevent the array from overflowing its storage. This makes +/// this type more difficult to use than a dynamic array. However, it allows +/// this construct to provide predictably stable performance. +/// +/// This trading of usability in favor of stable performance limits `RigidArray` +/// to the most resource-constrained of use cases, such as space-constrained +/// environments that require carefully accounting of every heap allocation, or +/// time-constrained applications that cannot accommodate unexpected latency +/// spikes due to a reallocation getting triggered at an inopportune moment. +/// +/// For use cases outside of these narrow domains, we generally recommmend +/// the use of ``DynamicArray`` rather than `RigidArray`. +@safe +@frozen +public struct RigidArray: ~Copyable { + @usableFromInline + internal var _storage: UnsafeMutableBufferPointer + + @usableFromInline + internal var _count: Int + + deinit { + unsafe _storage.extracting(0 ..< _count).deinitialize() + unsafe _storage.deallocate() + } + + /// Initializes a new rigid array with the specified capacity and no elements. + @inlinable + public init(capacity: Int) { + precondition(capacity >= 0, "Array capacity must be nonnegative") + if capacity > 0 { + unsafe _storage = .allocate(capacity: capacity) + } else { + unsafe _storage = .init(start: nil, count: 0) + } + _count = 0 + } + + /// Initializes a new rigid array with zero capacity and no elements. + @inlinable + public init() { + unsafe _storage = .init(start: nil, count: 0) + _count = 0 + } +} +extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} + +//MARK: - Initializers + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @inlinable + public init( + capacity: Int, + initializedWith body: (inout OutputSpan) throws(E) -> Void + ) throws(E) { + self.init(capacity: capacity) + try edit(body) + } +} + +extension RigidArray /*where Element: Copyable*/ { + /// Creates a new array containing the specified number of a single, + /// repeated value. + /// + /// - Parameters: + /// - repeatedValue: The element to repeat. + /// - count: The number of times to repeat the value passed in the + /// `repeating` parameter. `count` must be zero or greater. + public init(repeating repeatedValue: Element, count: Int) { + self.init(capacity: count) + unsafe _freeSpace.initialize(repeating: repeatedValue) + _count = count + } +} + +extension RigidArray /*where Element: Copyable*/ { + @_alwaysEmitIntoClient + @inline(__always) + public init( + capacity: Int, + copying contents: some Sequence + ) { + self.init(capacity: capacity) + self.append(copying: contents) + } + + @_alwaysEmitIntoClient + @inline(__always) + public init( + capacity: Int? = nil, + copying contents: some Collection + ) { + self.init(capacity: capacity ?? contents.count) + self.append(copying: contents) + } +} + +// FIXME: init(moving:), init(consuming:) + +//MARK: - Basics + +extension RigidArray where Element: ~Copyable { + @inlinable + @_transparent + public var capacity: Int { _assumeNonNegative(unsafe _storage.count) } + + @inlinable + @_transparent + public var freeCapacity: Int { + _assumeNonNegative(capacity &- count) + } + + @inlinable + @inline(__always) + public var isFull: Bool { freeCapacity == 0 } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + internal var _items: UnsafeMutableBufferPointer { + unsafe _storage.extracting(Range(uncheckedBounds: (0, _count))) + } + + @inlinable + internal var _freeSpace: UnsafeMutableBufferPointer { + unsafe _storage.extracting(Range(uncheckedBounds: (_count, capacity))) + } +} + +//MARK: - Span creation + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + public var span: Span { + @_lifetime(borrow self) + @inlinable + get { + let result = unsafe Span(_unsafeElements: _items) + return unsafe _overrideLifetime(result, borrowing: self) + } + } + + @available(SwiftStdlib 5.0, *) + public var mutableSpan: MutableSpan { + @_lifetime(&self) + @inlinable + mutating get { + let result = unsafe MutableSpan(_unsafeElements: _items) + return unsafe _overrideLifetime(result, mutating: &self) + } + } + + @available(SwiftStdlib 5.0, *) + @inlinable + @_lifetime(borrow self) + internal func _span(in range: Range) -> Span { + span.extracting(range) + } + + @available(SwiftStdlib 5.0, *) + @inlinable + @_lifetime(&self) + internal mutating func _mutableSpan( + in range: Range + ) -> MutableSpan { + let result = unsafe MutableSpan(_unsafeElements: _items.extracting(range)) + return unsafe _overrideLifetime(result, mutating: &self) + } +} + +extension RigidArray where Element: ~Copyable { + /// Arbitrarily edit the storage underlying this array by invoking a + /// user-supplied closure with a mutable `OutputSpan` view over it. + /// This method calls its function argument precisely once, allowing it to + /// arbitrarily modify the contents of the output span it is given. + /// The argument is free to add, remove or reorder any items; however, + /// it is not allowed to replace the span or change its capacity. + /// + /// When the function argument finishes (whether by returning or throwing an + /// error) the rigid array instance is updated to match the final contents of + /// the output span. + /// + /// - Parameter body: A function that edits the contents of this array through + /// an `OutputSpan` argument. This method invokes this function + /// precisely once. + /// - Returns: This method returns the result of its function argument. + /// - Complexity: Adds O(1) overhead to the complexity of the function + /// argument. + @available(SwiftStdlib 5.0, *) + @inlinable + public mutating func edit( + _ body: (inout OutputSpan) throws(E) -> R + ) throws(E) -> R { + var span = OutputSpan(buffer: _storage, initializedCount: _count) + defer { + _count = span.finalize(for: _storage) + span = OutputSpan() + } + return try body(&span) + } + + // FIXME: Stop using and remove this in favor of `edit` + @unsafe + @inlinable + internal mutating func _unsafeEdit( + _ body: (UnsafeMutableBufferPointer, inout Int) throws(E) -> R + ) throws(E) -> R { + defer { precondition(_count >= 0 && _count <= capacity) } + return unsafe try body(_storage, &_count) + } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + internal func _contiguousSubrange(following index: inout Int) -> Range { + precondition(index >= 0 && index <= _count, "Index out of bounds") + defer { index = _count } + return unsafe Range(uncheckedBounds: (index, _count)) + } + + @inlinable + internal func _contiguousSubrange(preceding index: inout Int) -> Range { + precondition(index >= 0 && index <= _count, "Index out of bounds") + defer { index = 0 } + return unsafe Range(uncheckedBounds: (0, index)) + } +} + +//MARK: - Random-access & mutable container primitives + +extension RigidArray where Element: ~Copyable { + public typealias Index = Int + + @inlinable + @inline(__always) + public var isEmpty: Bool { count == 0 } + + @inlinable + @inline(__always) + public var count: Int { _count } + + @inlinable + @inline(__always) + public var startIndex: Int { 0 } + + @inlinable + @inline(__always) + public var endIndex: Int { count } + + @inlinable + @inline(__always) + public var indices: Range { unsafe Range(uncheckedBounds: (0, count)) } +} + +extension RigidArray where Element: ~Copyable { + @inlinable @inline(__always) + internal func _ptr(to index: Int) -> UnsafePointer { + precondition(index >= 0 && index < _count, "Index out of bounds") + let p = _storage.baseAddress.unsafelyUnwrapped.advanced(by: index) + return UnsafePointer(p) + } + + @inlinable @inline(__always) + internal mutating func _mutablePtr( + to index: Int + ) -> UnsafeMutablePointer { + precondition(index >= 0 && index < _count, "Index out of bounds") + return _storage.baseAddress.unsafelyUnwrapped.advanced(by: index) + } + + @inlinable + public subscript(position: Int) -> Element { + unsafeAddress { + _ptr(to: position) + } + unsafeMutableAddress { + _mutablePtr(to: position) + } + } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + precondition( + i >= 0 && i < _count && j >= 0 && j < _count, + "Index out of bounds") + unsafe _items.swapAt(i, j) + } +} + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @inlinable + @_lifetime(borrow self) + public func span(after index: inout Int) -> Span { + _span(in: _contiguousSubrange(following: &index)) + } + + @available(SwiftStdlib 5.0, *) + @inlinable + @_lifetime(borrow self) + public func span(before index: inout Int) -> Span { + _span(in: _contiguousSubrange(preceding: &index)) + } +} + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 5.0, *) + @_lifetime(&self) + public mutating func mutableSpan( + after index: inout Int + ) -> MutableSpan { + _mutableSpan(in: _contiguousSubrange(following: &index)) + } + + @available(SwiftStdlib 5.0, *) + @_lifetime(&self) + public mutating func mutableSpan( + before index: inout Int + ) -> MutableSpan { + _mutableSpan(in: _contiguousSubrange(preceding: &index)) + } +} + +//MARK: - Resizing + +extension RigidArray where Element: ~Copyable { + @inlinable + public mutating func reallocate(capacity newCapacity: Int) { + precondition(newCapacity >= count, "RigidArray capacity overflow") + guard newCapacity != capacity else { return } + let newStorage: UnsafeMutableBufferPointer = .allocate( + capacity: newCapacity) + let i = unsafe newStorage.moveInitialize(fromContentsOf: self._items) + assert(i == count) + unsafe _storage.deallocate() + unsafe _storage = newStorage + } + + @inlinable + public mutating func reserveCapacity(_ n: Int) { + guard capacity < n else { return } + reallocate(capacity: n) + } +} + +//MARK: - Copying helpers + +extension RigidArray { + @inlinable + public func copy() -> Self { + copy(capacity: capacity) + } + + @inlinable + public func copy(capacity: Int) -> Self { + precondition(capacity >= count, "RigidArray capacity overflow") + var result = RigidArray(capacity: capacity) + let initialized = unsafe result._storage.initialize(fromContentsOf: _items) + precondition(initialized == count) + result._count = count + return result + } +} + + +//MARK: - Opening and closing gaps + +extension RigidArray where Element: ~Copyable { + @inlinable + internal mutating func _closeGap( + at index: Int, count: Int + ) { + guard count > 0 else { return } + let source = unsafe _storage.extracting( + Range(uncheckedBounds: (index + count, _count))) + let target = unsafe _storage.extracting( + Range(uncheckedBounds: (index, index + source.count))) + let i = unsafe target.moveInitialize(fromContentsOf: source) + assert(i == target.endIndex) + } + + @inlinable + @unsafe + internal mutating func _openGap( + at index: Int, count: Int + ) -> UnsafeMutableBufferPointer { + assert(index >= 0 && index <= _count) + assert(count <= freeCapacity) + guard count > 0 else { return unsafe _storage.extracting(index ..< index) } + let source = unsafe _storage.extracting( + Range(uncheckedBounds: (index, _count))) + let target = unsafe _storage.extracting( + Range(uncheckedBounds: (index + count, _count + count))) + let i = unsafe target.moveInitialize(fromContentsOf: source) + assert(i == target.count) + return unsafe _storage.extracting( + Range(uncheckedBounds: (index, index + count))) + } +} + +//MARK: - Removal operations + +extension RigidArray where Element: ~Copyable { + /// Removes all elements from the array, preserving its allocated capacity. + /// + /// - Complexity: O(*n*), where *n* is the original count of the array. + @inlinable + public mutating func removeAll() { + unsafe _items.deinitialize() + _count = 0 + } + + /// Removes and returns the last element of the array. + /// + /// The array must not be empty. + /// + /// - Returns: The last element of the original array. + /// + /// - Complexity: O(1) + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + precondition(!isEmpty, "Cannot remove last element from an empty array") + let old = unsafe _storage.moveElement(from: _count - 1) + _count -= 1 + return old + } + + /// Removes and discards the specified number of elements from the end of the + /// array. + /// + /// Attempting to remove more elements than exist in the array + /// triggers a runtime error. + /// + /// - Parameter k: The number of elements to remove from the array. + /// `k` must be greater than or equal to zero and must not exceed + /// the count of the array. + /// + /// - Complexity: O(`k`) + @inlinable + public mutating func removeLast(_ k: Int) { + if k == 0 { return } + precondition( + k >= 0 && k <= _count, + "Count of elements to remove is out of bounds") + unsafe _storage.extracting( + Range(uncheckedBounds: (_count - k, _count)) + ).deinitialize() + _count &-= k + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// gap. + /// + /// - Parameter i: The position of the element to remove. `index` must be + /// a valid index of the array that is not equal to the end index. + /// - Returns: The removed element. + /// + /// - Complexity: O(`count`) + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + precondition(index >= 0 && index < _count, "Index out of bounds") + let old = unsafe _storage.moveElement(from: index) + _closeGap(at: index, count: 1) + _count -= 1 + return old + } + + /// Removes the specified subrange of elements from the array. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + precondition( + bounds.lowerBound >= 0 && bounds.upperBound <= _count, + "Subrange out of bounds") + guard !bounds.isEmpty else { return } + unsafe _storage.extracting(bounds).deinitialize() + _closeGap(at: bounds.lowerBound, count: bounds.count) + _count -= bounds.count + } + + /// Removes the specified subrange of elements from the array. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds of the + /// range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + public mutating func removeSubrange(_ bounds: some RangeExpression) { + // FIXME: Remove this in favor of a standard algorithm. + removeSubrange(bounds.relative(to: indices)) + } +} + +extension RigidArray where Element: ~Copyable { + /// Removes and returns the last element of the array, if there is one. + /// + /// - Returns: The last element of the array if the array is not empty; + /// otherwise, `nil`. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public mutating func popLast() -> Element? { + // FIXME: Remove this in favor of a standard algorithm. + if isEmpty { return nil } + return removeLast() + } +} + +//MARK: - Append operations + +extension RigidArray where Element: ~Copyable { + /// Adds an element to the end of the array. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this triggers a runtime error. + /// + /// - Parameter item: The element to append to the collection. + /// + /// - Complexity: O(1) + @inlinable + public mutating func append(_ item: consuming Element) { + precondition(!isFull, "RigidArray capacity overflow") + unsafe _storage.initializeElement(at: _count, to: item) + _count &+= 1 + } + + /// Adds an element to the end of the array, if possible. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this returns the given item without appending it; otherwise it + /// returns nil. + /// + /// - Parameter item: The element to append to the collection. + /// - Returns: `item` if the array is full; otherwise nil. + /// + /// - Complexity: O(1) + @inlinable + public mutating func pushLast(_ item: consuming Element) -> Element? { + // FIXME: Remove this in favor of a standard algorithm. + if isFull { return item } + append(item) + return nil + } +} + +extension RigidArray where Element: ~Copyable { + /// Moves the elements of a buffer to the end of this array, leaving the + /// buffer uninitialized. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func append( + moving items: UnsafeMutableBufferPointer + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + let c = unsafe _freeSpace._moveInitializePrefix(from: items) + assert(c == items.count) + _count &+= items.count + } + + /// Appends the elements of a given array to the end of this array by moving + /// them between the containers. On return, the input array becomes empty, but + /// it is not destroyed, and it preserves its original storage capacity. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: An array whose items to move to the end of this array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func append( + moving items: inout RigidArray + ) { + // FIXME: Remove this in favor of a generic algorithm over range-replaceable containers + unsafe items._unsafeEdit { buffer, count in + let source = buffer._extracting(first: count) + unsafe self.append(moving: source) + count = 0 + } + } +} + +extension RigidArray where Element: ~Copyable { + /// Appends the elements of a given container to the end of this array by + /// consuming the source container. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func append( + consuming items: consuming RigidArray + ) { + // FIXME: Remove this in favor of a generic algorithm over consumable containers + var items = items + self.append(moving: &items) + } +} + +extension RigidArray { + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) + @_alwaysEmitIntoClient + public mutating func append( + copying newElements: UnsafeBufferPointer + ) { + precondition( + newElements.count <= freeCapacity, + "RigidArray capacity overflow") + guard newElements.count > 0 else { return } + unsafe _freeSpace.baseAddress.unsafelyUnwrapped.initialize( + from: newElements.baseAddress.unsafelyUnwrapped, count: newElements.count) + _count &+= newElements.count + } + + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) + @_alwaysEmitIntoClient + public mutating func append( + copying items: UnsafeMutableBufferPointer + ) { + unsafe self.append(copying: UnsafeBufferPointer(items)) + } + + /// Copies the elements of a span to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// span, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A span whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public mutating func append(copying items: Span) { + unsafe items.withUnsafeBufferPointer { source in + unsafe self.append(copying: source) + } + } + + @_alwaysEmitIntoClient + @inline(__always) + internal mutating func _append>( + prefixOf items: S + ) -> S.Iterator { + let (it, c) = unsafe items._copyContents(initializing: _freeSpace) + _count += c + return it + } + + /// Copies the elements of a sequence to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// sequence, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`. + @_alwaysEmitIntoClient + public mutating func append(copying newElements: some Sequence) { + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + unsafe self.append(copying: buffer) + return + } + if done != nil { return } + + var it = self._append(prefixOf: newElements) + precondition(it.next() == nil, "RigidArray capacity overflow") + } +} + +//MARK: - Insert operations + + +extension RigidArray where Element: ~Copyable { + /// Inserts a new element into the array at the specified position. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this triggers a runtime error. + /// + /// The new element is inserted before the element currently at the specified + /// index. If you pass the array's `endIndex` as the `index` parameter, then + /// the new element is appended to the container. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// - Parameter item: The new element to insert into the array. + /// - Parameter i: The position at which to insert the new element. + /// `index` must be a valid index in the array. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count, "Index out of bounds") + precondition(!isFull, "RigidArray capacity overflow") + if index < count { + let source = unsafe _storage.extracting(index ..< count) + let target = unsafe _storage.extracting(index + 1 ..< count + 1) + let last = unsafe target.moveInitialize(fromContentsOf: source) + assert(last == target.endIndex) + } + unsafe _storage.initializeElement(at: index, to: item) + _count += 1 + } +} + +extension RigidArray where Element: ~Copyable { + /// Moves the elements of a fully initialized buffer into this array, + /// starting at the specified position, and leaving the buffer + /// uninitialized. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`count` + `items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: UnsafeMutableBufferPointer, + at index: Int + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + let target = unsafe _openGap(at: index, count: items.count) + let c = unsafe target._moveInitializePrefix(from: items) + assert(c == items.count) + _count &+= items.count + } + + /// Inserts the elements of a given array into the given position in this + /// array by moving them between the containers. On return, the input array + /// becomes empty, but it is not destroyed, and it preserves its original + /// storage capacity. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: An array whose contents to move into `self`. + /// + /// - Complexity: O(`count` + `items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: inout RigidArray, + at index: Int + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + unsafe items._unsafeEdit { buffer, count in + let source = buffer._extracting(first: count) + unsafe self.insert(moving: source, at: index) + count = 0 + } + } +} + +extension RigidArray where Element: ~Copyable { + /// Inserts the elements of a given array into the given position in this + /// array by consuming the source container. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`count` + `items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + consuming items: consuming RigidArray, + at index: Int + ) { + var items = items + self.insert(moving: &items, at: index) + } +} + +extension RigidArray { + /// Copies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(`count` + `newElements.count`) + @inlinable + public mutating func insert( + copying newElements: UnsafeBufferPointer, at index: Int + ) { + guard newElements.count > 0 else { return } + precondition( + newElements.count <= freeCapacity, + "RigidArray capacity overflow") + let gap = unsafe _openGap(at: index, count: newElements.count) + unsafe gap.baseAddress.unsafelyUnwrapped.initialize( + from: newElements.baseAddress.unsafelyUnwrapped, count: newElements.count) + _count += newElements.count + } + + /// Copies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(`count` + `newElements.count`) + @inlinable + public mutating func insert( + copying newElements: UnsafeMutableBufferPointer, + at index: Int + ) { + unsafe self.insert(copying: UnsafeBufferPointer(newElements), at: index) + } + + /// Copies the elements of a span into this array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(`count` + `newElements.count`) + @available(SwiftStdlib 5.0, *) + @inlinable + public mutating func insert( + copying newElements: Span, at index: Int + ) { + unsafe newElements.withUnsafeBufferPointer { + unsafe self.insert(copying: $0, at: index) + } + } + + @inlinable + internal mutating func _insertCollection( + at index: Int, + copying items: some Collection, + newCount: Int + ) { + precondition(index >= 0 && index <= _count, "Index out of bounds") + precondition(newCount <= freeCapacity, "RigidArray capacity overflow") + let gap = unsafe _openGap(at: index, count: newCount) + + let done: Void? = items.withContiguousStorageIfAvailable { buffer in + let i = unsafe gap._initializePrefix(copying: buffer) + precondition( + i == newCount, + "Broken Collection: count doesn't match contents") + _count += newCount + } + if done != nil { return } + + var (it, copied) = unsafe items._copyContents(initializing: gap) + precondition( + it.next() == nil && copied == newCount, + "Broken Collection: count doesn't match contents") + _count += newCount + } + + /// Copies the elements of a collection into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved + /// to make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(`count` + `newElements.count`) + @inlinable + @inline(__always) + public mutating func insert( + copying newElements: some Collection, at index: Int + ) { + _insertCollection( + at: index, copying: newElements, newCount: newElements.count) + } +} + +//MARK: - Range replacement + +extension RigidArray where Element: ~Copyable { + /// Perform a range replacement up to populating the newly opened gap. This + /// deinitializes existing elements in the specified subrange, rearranges + /// following elements to be at their final location, and sets the container's + /// new count. + /// + /// - Returns: A buffer pointer addressing the newly opened gap, to be + /// initialized by the caller. + @inlinable + @unsafe + internal mutating func _gapForReplacement( + of subrange: Range, withNewCount newCount: Int + ) -> UnsafeMutableBufferPointer { + // FIXME: Replace this with a public variant based on OutputSpan. + precondition( + subrange.lowerBound >= 0 && subrange.upperBound <= _count, + "Index range out of bounds") + precondition( + newCount - subrange.count <= freeCapacity, + "RigidArray capacity overflow") + unsafe _items.extracting(subrange).deinitialize() + if newCount > subrange.count { + _ = unsafe _openGap( + at: subrange.upperBound, count: newCount - subrange.count) + } else if newCount < subrange.count { + _closeGap( + at: subrange.lowerBound + newCount, count: subrange.count - newCount) + } + _count += newCount - subrange.count + let gapRange = unsafe Range( + uncheckedBounds: (subrange.lowerBound, subrange.lowerBound + newCount)) + return unsafe _storage.extracting(gapRange) + } +} + +extension RigidArray where Element: ~Copyable { + /// Replaces the specified range of elements by moving the elements of a + /// fully initialized buffer into their place. On return, the buffer is left + /// in an uninitialized state. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + moving newElements: UnsafeMutableBufferPointer, + ) { + let gap = unsafe _gapForReplacement( + of: subrange, withNewCount: newElements.count) + let c = unsafe gap._moveInitializePrefix(from: newElements) + assert(c == newElements.count) + } + + /// Replaces the specified range of elements by moving the elements of a + /// another array into their place. On return, the source array + /// becomes empty, but it is not destroyed, and it preserves its original + /// storage capacity. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + moving newElements: inout RigidArray, + ) { + unsafe newElements._unsafeEdit { buffer, count in + let source = buffer._extracting(first: count) + unsafe self.replaceSubrange(subrange, moving: source) + count = 0 + } + } +} + +extension RigidArray where Element: ~Copyable { + /// Replaces the specified range of elements by moving the elements of a + /// given array into their place, consuming it in the process. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + consuming newElements: consuming RigidArray, + ) { + replaceSubrange(subrange, moving: &newElements) + } +} + +extension RigidArray { + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeBufferPointer + ) { + let gap = unsafe _gapForReplacement( + of: subrange, withNewCount: newElements.count) + let i = unsafe gap._initializePrefix(copying: newElements) + assert(i == gap.count) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeMutableBufferPointer + ) { + unsafe self.replaceSubrange( + subrange, + copying: UnsafeBufferPointer(newElements)) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given span. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length span as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @available(SwiftStdlib 5.0, *) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: Span + ) { + unsafe newElements.withUnsafeBufferPointer { buffer in + unsafe self.replaceSubrange(subrange, copying: buffer) + } + } + + @inlinable + internal mutating func _replaceSubrange( + _ subrange: Range, + copyingCollection newElements: __owned some Collection, + newCount: Int + ) { + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: newCount) + + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + let i = unsafe gap._initializePrefix(copying: buffer) + precondition( + i == newCount, + "Broken Collection: count doesn't match contents") + } + if done != nil { return } + + var (it, copied) = unsafe newElements._copyContents(initializing: gap) + precondition( + it.next() == nil && copied == newCount, + "Broken Collection: count doesn't match contents") + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given collection. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length collection as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: __owned some Collection + ) { + _replaceSubrange( + subrange, copyingCollection: newElements, newCount: newElements.count) + } +} +#endif diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 7c4fe2a69..e98233cb8 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -54,7 +54,11 @@ else() add_subdirectory(Collections) endif() + +add_subdirectory(InternalCollectionsUtilities) +add_subdirectory(ContainersPreview) if(NOT COLLECTIONS_FOUNDATION_TOOLCHAIN_MODULE) + add_subdirectory(ArrayModule) add_subdirectory(BitCollections) add_subdirectory(HashTreeCollections) add_subdirectory(HeapModule) @@ -62,4 +66,3 @@ endif() add_subdirectory(DequeModule) add_subdirectory(OrderedCollections) add_subdirectory(RopeModule) -add_subdirectory(InternalCollectionsUtilities) diff --git a/Sources/ContainersPreview/Borrow.swift b/Sources/ContainersPreview/Borrow.swift new file mode 100644 index 000000000..7d42bbf2d --- /dev/null +++ b/Sources/ContainersPreview/Borrow.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +import Builtin + +@frozen +@safe +public struct Borrow: Copyable, ~Escapable { + @usableFromInline + package let _pointer: UnsafePointer + +#if compiler(>=6.2) && FIXME + @_lifetime(borrow value) + @_alwaysEmitIntoClient + @_transparent + public init(_ value: borrowing @_addressable T) { + unsafe _pointer = UnsafePointer(Builtin.unprotectedAddressOfBorrow(value)) + } +#endif + + @_lifetime(borrow owner) + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeAddress: UnsafePointer, + borrowing owner: borrowing Owner + ) { + unsafe _pointer = unsafeAddress + } + + @_lifetime(copy owner) + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeAddress: UnsafePointer, + copying owner: borrowing Owner + ) { + unsafe _pointer = unsafeAddress + } + + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + unsafe _pointer + } + } +} +#endif diff --git a/Sources/ContainersPreview/Box.swift b/Sources/ContainersPreview/Box.swift new file mode 100644 index 000000000..87a136e6b --- /dev/null +++ b/Sources/ContainersPreview/Box.swift @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.2) +@frozen +@safe +public struct Box: ~Copyable { + @usableFromInline + internal let _pointer: UnsafeMutablePointer + + @_alwaysEmitIntoClient + @_transparent + public init(_ value: consuming T) { + unsafe _pointer = UnsafeMutablePointer.allocate(capacity: 1) + unsafe _pointer.initialize(to: value) + } + + @_alwaysEmitIntoClient + @inlinable + deinit { + unsafe _pointer.deinitialize(count: 1) + unsafe _pointer.deallocate() + } +} + +extension Box where T: ~Copyable { + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + unsafe UnsafePointer(_pointer) + } + + @_transparent + unsafeMutableAddress { + unsafe _pointer + } + } + + @_alwaysEmitIntoClient + @_transparent + public consuming func consume() -> T { + let result = unsafe _pointer.move() + unsafe _pointer.deallocate() + discard self + return result + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + @_alwaysEmitIntoClient + @_transparent + @_lifetime(immortal) + public consuming func leak() -> Inout { + let result = unsafe Inout(unsafeImmortalAddress: _pointer) + discard self + return result + } +#endif + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + @_alwaysEmitIntoClient + @_transparent + @_lifetime(borrow self) + public func borrow() -> Borrow { + unsafe Borrow(unsafeAddress: UnsafePointer(_pointer), borrowing: self) + } +#endif + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + @_alwaysEmitIntoClient + @_transparent + @_lifetime(&self) + public mutating func mutate() -> Inout { + unsafe Inout(unsafeAddress: _pointer, mutating: &self) + } +#endif +} + +extension Box where T: Copyable { + @_alwaysEmitIntoClient + @_transparent + public borrowing func copy() -> T { + unsafe _pointer.pointee + } +} + +extension Box where T: ~Copyable { + @available(SwiftStdlib 5.0, *) + public var span: Span { + @_alwaysEmitIntoClient + @_lifetime(borrow self) + get { + unsafe Span(_unsafeStart: _pointer, count: 1) + } + } + + @available(SwiftStdlib 5.0, *) + public var mutableSpan: MutableSpan { + @_alwaysEmitIntoClient + @_lifetime(&self) + mutating get { + unsafe MutableSpan(_unsafeStart: _pointer, count: 1) + } + } +} +#endif diff --git a/Sources/ContainersPreview/CMakeLists.txt b/Sources/ContainersPreview/CMakeLists.txt new file mode 100644 index 000000000..8af823677 --- /dev/null +++ b/Sources/ContainersPreview/CMakeLists.txt @@ -0,0 +1,31 @@ +#[[ +This source file is part of the Swift Collections Open Source Project + +Copyright (c) 2022 - 2025 Apple Inc. and the Swift project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +if(COLLECTIONS_SINGLE_MODULE) + set(module_name ${COLLECTIONS_MODULE_NAME}) +else() + set(module_name ContainersPreview) + add_library(ContainersPreview + ${COLLECTIONS_CONTAINERS_SOURCES}) + target_link_libraries(ContainersPreview PRIVATE + InternalCollectionsUtilities) + set_target_properties(ContainersPreview PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + + _install_target(ContainersPreview) + set_property(GLOBAL APPEND PROPERTY SWIFT_COLLECTIONS_EXPORTS ContainersPreview) +endif() + +target_sources(${module_name} PRIVATE + "Borrow.swift" + "Box.swift" + "Inout.swift" + "InputSpan.swift" + "Shared.swift" +) diff --git a/Sources/ContainersPreview/Inout.swift b/Sources/ContainersPreview/Inout.swift new file mode 100644 index 000000000..bf759e6da --- /dev/null +++ b/Sources/ContainersPreview/Inout.swift @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +import Builtin + +// FIXME: A better name for the generic argument. + +/// A safe mutable reference allowing in-place mutation to an exclusive value. +/// +/// In order to get an instance of an `Inout`, one must have exclusive access +/// to the instance of `T`. This is achieved through the 'inout' operator, '&'. +@frozen +@safe +public struct Inout: ~Copyable, ~Escapable { + @usableFromInline + package let _pointer: UnsafeMutablePointer + + /// Initializes an instance of 'Inout' extending the exclusive access of the + /// passed instance. + /// + /// - Parameter instance: The desired instance to get a mutable reference to. + @_alwaysEmitIntoClient + @_transparent + public init(_ instance: inout T) { + unsafe _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) + } + + /// Unsafely initializes an instance of 'Inout' using the given 'unsafeAddress' + /// as the mutable reference based on the lifetime of the given 'owner' + /// argument. + /// + /// - Parameter unsafeAddress: The address to use to mutably reference an + /// instance of type 'T'. + /// - Parameter owner: The owning instance that this 'Inout' instance's + /// lifetime is based on. + @unsafe + @_alwaysEmitIntoClient + @_transparent + @_lifetime(&owner) + public init( + unsafeAddress: UnsafeMutablePointer, + mutating owner: inout Owner + ) { + unsafe _pointer = unsafeAddress + } + + /// Unsafely initializes an instance of 'Inout' using the given + /// 'unsafeImmortalAddress' as the mutable reference acting as though its + /// lifetime is immortal. + /// + /// - Parameter unsafeImmortalAddress: The address to use to mutably reference + /// an immortal instance of type 'T'. + @_lifetime(immortal) + @unsafe + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeImmortalAddress: UnsafeMutablePointer + ) { + unsafe _pointer = unsafeImmortalAddress + } +} + +extension Inout where T: ~Copyable { + /// Dereferences the mutable reference allowing for in-place reads and writes + /// to the underlying instance. + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + unsafe UnsafePointer(_pointer) + } + + @_lifetime(copy self) + @_transparent + unsafeMutableAddress { + unsafe _pointer + } + } +} +#endif diff --git a/Sources/ContainersPreview/InputSpan.swift b/Sources/ContainersPreview/InputSpan.swift new file mode 100644 index 000000000..0a9453044 --- /dev/null +++ b/Sources/ContainersPreview/InputSpan.swift @@ -0,0 +1,462 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif +import Builtin + +@safe +@frozen +public struct InputSpan: ~Copyable, ~Escapable { + @usableFromInline + internal let _pointer: UnsafeMutableRawPointer? + + public let capacity: Int + + @usableFromInline + internal var _count: Int + + @_alwaysEmitIntoClient + @inlinable + deinit { + if _count > 0 { + let c = _count + unsafe _first().withMemoryRebound(to: Element.self, capacity: _count) { + _ = unsafe $0.deinitialize(count: c) + } + } + } + + @_lifetime(immortal) + public init() { + _pointer = nil + capacity = 0 + _count = 0 + } +} + +extension InputSpan: @unchecked Sendable where Element: Sendable & ~Copyable {} + +extension InputSpan where Element: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + @unsafe + internal func _start() -> UnsafeMutableRawPointer { + unsafe _pointer.unsafelyUnwrapped + } + + @_alwaysEmitIntoClient + @_transparent + @unsafe + internal func _first() -> UnsafeMutableRawPointer { + // NOTE: `_pointer` must be known to be not-nil. + unsafe _start().advanced(by: _count &* MemoryLayout.stride) + } + + @unsafe + @_alwaysEmitIntoClient + @_transparent + internal func _unsafeRawAddressOfSlot( + uncheckedOffset offset: Int + ) -> UnsafeMutableRawPointer { + let offset = (capacity &- _count &+ offset) &* MemoryLayout.stride + return unsafe _start().advanced(by: offset) + } + + @unsafe + @_alwaysEmitIntoClient + @_transparent + internal func _unsafeAddressOfElement( + unchecked index: Index + ) -> UnsafeMutablePointer { + _unsafeRawAddressOfSlot( + uncheckedOffset: index + ).assumingMemoryBound(to: Element.self) + } +} + +extension InputSpan where Element: ~Copyable { + /// Consume the input span and return the number of initialized elements + /// remaining at the end of the underlying memory region. + /// + /// This method should be invoked in the scope where the `InputSpan` was + /// created, when it is time to commit the contents of the updated buffer + /// back into the construct that was accessed. + /// + /// The context that created the input span is expected to remember what + /// memory region the span is addressing. This consuming method expects to + /// receive a copy of the same buffer pointer as a (loose) proof of ownership. + /// + /// - Parameter buffer: The buffer we expect the `InputSpan` to reference. + /// This must be the same region of memory passed to + /// the `InputSpan` initializer. + /// - Returns: The number of initialized elements remaining at the end of the + /// underlying buffer. + @unsafe + @_alwaysEmitIntoClient + public consuming func finalize( + for buffer: UnsafeMutableBufferPointer + ) -> Int { + precondition( + unsafe UnsafeMutableRawPointer(buffer.baseAddress) == self._pointer + && buffer.count == self.capacity, + "InputSpan identity mismatch") + let count = self._count + discard self + return count + } +} + +extension InputSpan { + /// Consume the input span and return the number of initialized elements + /// remaining at the end of the underlying memory region. + /// + /// This method should be invoked in the scope where the `InputSpan` was + /// created, when it is time to commit the contents of the updated buffer + /// back into the construct that was accessed. + /// + /// The context that created the input span is expected to remember what + /// memory region the span is addressing. This consuming method expects to + /// receive a copy of the same buffer pointer as a (loose) proof of ownership. + /// + /// - Parameter buffer: The buffer we expect the `InputSpan` to reference. + /// This must be the same region of memory passed to + /// the `InputSpan` initializer. + /// - Returns: The number of initialized elements remaining at the end of the + /// underlying buffer. + @unsafe + @_alwaysEmitIntoClient + public consuming func finalize( + for buffer: Slice> + ) -> Int { + unsafe finalize(for: UnsafeMutableBufferPointer(rebasing: buffer)) + } +} + +extension InputSpan where Element: ~Copyable { + /// The number of initialized elements in this span. + @_alwaysEmitIntoClient + public var count: Int { _count } + + /// The number of additional elements that can be added to this span. + @_alwaysEmitIntoClient + public var freeCapacity: Int { capacity &- _count } + + /// A Boolean value indicating whether the span is empty. + @_alwaysEmitIntoClient + public var isEmpty: Bool { _count == 0 } + + /// A Boolean value indicating whether the span is full. + @_alwaysEmitIntoClient + public var isFull: Bool { _count == capacity } +} + +extension InputSpan where Element: ~Copyable { + @unsafe + @_alwaysEmitIntoClient + @_lifetime(borrow buffer) + public init( + _uncheckedBuffer buffer: UnsafeMutableBufferPointer, + initializedCount: Int + ) { + unsafe _pointer = .init(buffer.baseAddress) + capacity = buffer.count + _count = initializedCount + } + + @unsafe + @_alwaysEmitIntoClient + @_lifetime(borrow buffer) + public init( + buffer: UnsafeMutableBufferPointer, initializedCount: Int + ) { + precondition(buffer._isWellAligned(), "Misaligned OutputSpan") + if let baseAddress = buffer.baseAddress { + precondition( + unsafe baseAddress.advanced(by: buffer.count) >= baseAddress, + "Buffer must not wrap around the address space") + } + precondition( + 0 <= initializedCount && initializedCount <= buffer.count, + "OutputSpan count is not within capacity") + unsafe self.init( + _uncheckedBuffer: buffer, initializedCount: initializedCount) + } +} + +extension InputSpan { + /// Unsafely create an input span over partially initialized memory. + /// + /// The memory in `buffer` must remain valid throughout the lifetime + /// of the newly-created `InputSpan`. There must be exactly + /// `initializedCount` initialized instances at the end of the buffer, + /// preceded by uninitialized memory. + /// + /// - Parameters: + /// - buffer: an `UnsafeMutableBufferPointer` with some number of + /// initialized elements, all compacted into the end of the buffer + /// - initializedCount: the number of initialized elements + /// at the end of `buffer`. + @unsafe + @_alwaysEmitIntoClient + @_lifetime(borrow buffer) + public init( + buffer: borrowing Slice>, + initializedCount: Int + ) { + let rebased = unsafe UnsafeMutableBufferPointer(rebasing: buffer) + let os = unsafe InputSpan( + buffer: rebased, initializedCount: initializedCount) + self = unsafe _overrideLifetime(os, borrowing: buffer) + } +} + +extension InputSpan where Element: ~Copyable { + /// The type that represents an initialized position in an `InputSpan`. + public typealias Index = Int + + /// The range of initialized positions for this `InputSpan`. + @_alwaysEmitIntoClient + public var indices: Range { + unsafe Range(uncheckedBounds: (0, _count)) + } + + /// Accesses the element at the specified position. + /// + /// - Parameter index: A valid index into this span. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public subscript(_ index: Index) -> Element { + unsafeAddress { + precondition(indices.contains(index), "Index out of bounds") + return unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: index)) + } + + @_lifetime(self: copy self) + unsafeMutableAddress { + precondition(indices.contains(index), "Index out of bounds") + return unsafe _unsafeAddressOfElement(unchecked: index) + } + } + + /// Accesses the element at the specified position. + /// + /// This subscript does not validate `position`; this is an unsafe operation. + /// + /// - Parameter index: A valid index into this span. + /// + /// - Complexity: O(1) + @unsafe + @_alwaysEmitIntoClient + public subscript(unchecked index: Index) -> Element { + unsafeAddress { + unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: index)) + } + @_lifetime(self: copy self) + unsafeMutableAddress { + unsafe _unsafeAddressOfElement(unchecked: index) + } + } + + /// Exchange the elements at the two given offsets + /// + /// - Parameter i: A valid index into this span. + /// - Parameter j: A valid index into this span. + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func swapAt(_ i: Index, _ j: Index) { + precondition(indices.contains(Index(i))) + precondition(indices.contains(Index(j))) + unsafe swapAt(unchecked: i, unchecked: j) + } + + /// Exchange the elements at the two given offsets + /// + /// This subscript does not validate `i` or `j`; this is an unsafe operation. + /// + /// - Parameter i: A valid index into this span. + /// - Parameter j: A valid index into this span. + @unsafe + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func swapAt(unchecked i: Index, unchecked j: Index) { + guard i != j else { return } + let pi = unsafe _unsafeAddressOfElement(unchecked: i) + let pj = unsafe _unsafeAddressOfElement(unchecked: j) + let temporary = unsafe pi.move() + unsafe pi.initialize(to: pj.move()) + unsafe pj.initialize(to: consume temporary) + } +} + +extension InputSpan where Element: ~Copyable { + /// Prepend a single element to this span. + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func prepend(_ value: consuming Element) { + precondition(_count < capacity, "InputSpan capacity overflow") + unsafe _unsafeRawAddressOfSlot( + uncheckedOffset: -1 + ).initializeMemory(as: Element.self, to: value) + _count &+= 1 + } + + /// Remove the last initialized element from this span. + /// + /// Returns the last element. The `InputSpan` must not be empty. + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func removeFirst() -> Element { + precondition(!isEmpty, "InputSpan underflow") + defer { _count &-= 1 } + return _unsafeAddressOfElement(unchecked: 0).move() + } + + /// Remove the last N elements of this span, returning the memory they occupy + /// to the uninitialized state. + /// + /// `n` must not be greater than `count` + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func removeFirst(_ k: Int) { + precondition(k >= 0, "Can't remove a negative number of elements") + precondition(k <= _count, "InputSpan underflow") + unsafe _unsafeRawAddressOfSlot( + uncheckedOffset: 0 + ).withMemoryRebound(to: Element.self, capacity: k) { + _ = unsafe $0.deinitialize(count: k) + } + _count &-= k + } + + /// Remove all this span's elements and return its memory + /// to the uninitialized state. + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func removeAll() { + _ = unsafe _start().withMemoryRebound(to: Element.self, capacity: _count) { + unsafe $0.deinitialize(count: _count) + } + _count = 0 + } +} + +//MARK: Bulk append functions + +extension InputSpan { + /// Repeatedly append an element to this span. + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func prepend(repeating repeatedValue: Element, count: Int) { + precondition(count <= freeCapacity, "InputSpan capacity overflow") + unsafe _unsafeRawAddressOfSlot( + uncheckedOffset: -count + ).initializeMemory(as: Element.self, repeating: repeatedValue, count: count) + _count &+= count + } +} + +extension InputSpan where Element: ~Copyable { + /// Borrow the underlying initialized memory for read-only access. + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public var span: Span { + @_lifetime(borrow self) + borrowing get { + let pointer = unsafe _pointer? + .assumingMemoryBound(to: Element.self) + .advanced(by: capacity &- _count) + let buffer = unsafe UnsafeBufferPointer(start: pointer, count: _count) + let span = unsafe Span(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, borrowing: self) + } + } + + /// Exclusively borrow the underlying initialized memory for mutation. + @available(SwiftStdlib 5.0, *) + @_alwaysEmitIntoClient + public var mutableSpan: MutableSpan { + @_lifetime(&self) + mutating get { + let pointer = unsafe _pointer? + .assumingMemoryBound(to: Element.self) + .advanced(by: capacity &- _count) + let buffer = unsafe UnsafeMutableBufferPointer( + start: pointer, count: _count) + let span = unsafe MutableSpan(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, borrowing: self) + } + } +} + +extension InputSpan where Element: ~Copyable { + /// Call the given closure with the unsafe buffer pointer addressed by this + /// InputSpan and a mutable reference to its count of initialized elements. + /// + /// This method provides a way to process or consume the contents of an + /// `InputSpan` using unsafe operations, such as by dispatching to code + /// written in legacy (memory-unsafe) languages. + /// + /// The supplied closure may process the buffer in any way it wants; however, + /// when it finishes (whether by returning or throwing), it must leave the + /// buffer in a state that satisfies the invariants of the input span: + /// + /// 1. The inout integer passed in as the second argument must be the exact + /// number of initialized items in the buffer passed in as the first + /// argument. + /// 2. These initialized elements must be located in a single contiguous + /// region located at the end of the buffer. The initialized region must + /// be preceded by uninitialized memory. + /// + /// This function cannot verify these two invariants, and therefore + /// this is an unsafe operation. Violating the invariants of `InputSpan` + /// may result in undefined behavior. + @_alwaysEmitIntoClient + @_lifetime(self: copy self) + public mutating func withUnsafeMutableBufferPointer( + _ body: ( + UnsafeMutableBufferPointer, + _ initializedCount: inout Int + ) throws(E) -> R + ) throws(E) -> R { + guard let start = unsafe _pointer, capacity > 0 else { + let buffer = UnsafeMutableBufferPointer(_empty: ()) + var initializedCount = 0 + defer { + precondition(initializedCount == 0, "InputSpan capacity overflow") + } + return unsafe try body(buffer, &initializedCount) + } + // bind memory by hand to sidestep alignment concerns + let binding = Builtin.bindMemory( + start._rawValue, capacity._builtinWordValue, Element.self + ) + defer { Builtin.rebindMemory(start._rawValue, binding) } + let buffer = unsafe UnsafeMutableBufferPointer( + /*_uncheckedStart*/start: .init(start._rawValue), count: capacity + ) + var initializedCount = self._count + defer { + precondition( + 0 <= initializedCount && initializedCount <= capacity, + "InputSpan capacity overflow" + ) + self._count = initializedCount + } + return unsafe try body(buffer, &initializedCount) + } +} + +#endif diff --git a/Sources/ContainersPreview/Shared.swift b/Sources/ContainersPreview/Shared.swift new file mode 100644 index 000000000..67b4e1fa6 --- /dev/null +++ b/Sources/ContainersPreview/Shared.swift @@ -0,0 +1,164 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + +import Builtin // For Shared.isIdentical + +/// A utility adapter that wraps a noncopyable storage type in a copy-on-write +/// struct, enabling efficient implementation of value semantics. The type +/// allows safe borrowing and mutating access to its storage, with minimal fuss. +/// +/// Like `ManagedBufferPointer`, this type is intended to be used within the +/// internal implementation of public types. Instances of it aren't designed +/// to be exposed as public. +@safe +@frozen +public struct Shared { + @usableFromInline + internal var _box: _Box + + @inlinable + public init(_ storage: consuming Storage) { + unsafe self._box = _Box(storage) + } +} + +extension Shared: @unchecked Sendable where Storage: Sendable & ~Copyable {} + +extension Shared where Storage: ~Copyable { + @unsafe + @usableFromInline + internal final class _Box { + @exclusivity(unchecked) + @usableFromInline + internal var storage: Storage + + @inlinable + internal init(_ storage: consuming Storage) { + unsafe self.storage = storage + } + } +} + +extension Shared where Storage: ~Copyable { + @inlinable + @inline(__always) + public mutating func isUnique() -> Bool { + unsafe isKnownUniquelyReferenced(&_box) + } + + /// - Returns true if this instance was already uniquely referenced. + @discardableResult + @inlinable + public mutating func ensureUnique( + cloner: (borrowing Storage) -> Storage + ) -> Bool { + if isUnique() { return true } + replace(using: cloner) + return false + } + + @inlinable + public mutating func replace( + using body: (borrowing Storage) -> Storage + ) { + unsafe _box = _Box(body(_box.storage)) + } + + @inlinable + public mutating func edit( + shared cloner: (borrowing Storage) -> Storage, + unique updater: (inout Storage) -> Void + ) { + if isUnique() { + unsafe updater(&_box.storage) + } else { + unsafe _box = _Box(cloner(_box.storage)) + } + } +} + +import struct SwiftShims.HeapObject + +extension Shared where Storage: ~Copyable { + // FIXME: Can we avoid hacks like this? If not, perhaps `_Box.storage` should be tail-allocated. + @inlinable + internal var _address: UnsafePointer { + // Adapted from _getUnsafePointerToStoredProperties + let p = unsafe ( + UnsafeRawPointer(Builtin.bridgeToRawPointer(_box)) + + MemoryLayout.size) + return unsafe p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + } + + @inlinable + internal var _mutableAddress: UnsafeMutablePointer { + // Adapted from _getUnsafePointerToStoredProperties + let p = unsafe ( + UnsafeMutableRawPointer(Builtin.bridgeToRawPointer(_box)) + + MemoryLayout.size) + return unsafe p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + } +} + +extension Shared where Storage: ~Copyable { + @inlinable + @inline(__always) + public var value: Storage { + @_lifetime(borrow self) + unsafeAddress { + unsafe _address + } + @_lifetime(&self) + unsafeMutableAddress { + precondition(isUnique()) + return unsafe _mutableAddress + } + } +} + +extension Shared where Storage: ~Copyable { + @inlinable + @_lifetime(borrow self) + public borrowing func borrow() -> Borrow { + // This is gloriously (and very explicitly) unsafe, as it should be. + // `Shared` is carefully constructed to guarantee that + // lifetime(self) == lifetime(_box.storage). + unsafe Borrow(unsafeAddress: _address, borrowing: self) + } + + + @inlinable + @_lifetime(&self) + public mutating func mutate() -> Inout { + // This is gloriously (and very explicitly) unsafe, as it should be. + // `Shared` is carefully constructed to guarantee that + // lifetime(self) == lifetime(_box.storage). + unsafe Inout(unsafeAddress: _mutableAddress, mutating: &self) + } +} + +extension Shared where Storage: ~Copyable { + @inlinable + public func isIdentical(to other: Self) -> Bool { + if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { + return unsafe self._box === other._box + } else { + // To call the standard `===`, we need to do `_SharedBox` -> AnyObject conversions + // that are only supported in the Swift 6+ runtime. + let a = unsafe Builtin.bridgeToRawPointer(self._box) + let b = unsafe Builtin.bridgeToRawPointer(other._box) + return Bool(Builtin.cmp_eq_RawPointer(a, b)) + } + } +} +#endif diff --git a/Sources/InternalCollectionsUtilities/CMakeLists.txt b/Sources/InternalCollectionsUtilities/CMakeLists.txt index 0adb28f1c..b61f764d4 100644 --- a/Sources/InternalCollectionsUtilities/CMakeLists.txt +++ b/Sources/InternalCollectionsUtilities/CMakeLists.txt @@ -23,9 +23,12 @@ endif() target_sources(${module_name} PRIVATE "autogenerated/Debugging.swift" "autogenerated/Descriptions.swift" + "autogenerated/LifetimeOverride.swift" "autogenerated/RandomAccessCollection+Offsets.swift" "autogenerated/UnsafeBufferPointer+Extras.swift" "autogenerated/UnsafeMutableBufferPointer+Extras.swift" + "autogenerated/UnsafeRawBufferPointer+Extras.swift" + "autogenerated/UnsafeMutableRawBufferPointer+Extras.swift" "IntegerTricks/autogenerated/FixedWidthInteger+roundUpToPowerOfTwo.swift" "IntegerTricks/autogenerated/Integer rank.swift" "IntegerTricks/autogenerated/UInt+first and last set bit.swift" diff --git a/Sources/InternalCollectionsUtilities/LifetimeOverride.swift.gyb b/Sources/InternalCollectionsUtilities/LifetimeOverride.swift.gyb new file mode 100644 index 000000000..fa8d6de66 --- /dev/null +++ b/Sources/InternalCollectionsUtilities/LifetimeOverride.swift.gyb @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +%{ + from gyb_utils import * +}% +${autogenerated_warning()} + +#if compiler(>=6.2) +% for modifier in visibility_levels: +${visibility_boilerplate(modifier)} +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with an immortal lifetime. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(immortal) +${modifier} func _unsafeImmortalize( + _ dependent: consuming T +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(borrow source) +${modifier} func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, borrowing source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` that inherits +/// all lifetime dependencies from the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(copy source) +${modifier} func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, copying source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's exclusive borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(&source) +${modifier} func _overrideLifetime< + T: ~Copyable & ~Escapable, + U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, + mutating source: inout U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} +% end +${visibility_boilerplate("end")} +#endif diff --git a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb index 603e3047e..ff9930603 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2021 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2021 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -24,5 +24,63 @@ extension UnsafeBufferPointer { return baseAddress.unsafelyUnwrapped + index } } + +extension UnsafeBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two `UnsafeBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + ${modifier} func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeBufferPointer where Element: ~Copyable { + /// Returns a buffer pointer containing the initial elements of this buffer, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// then the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + + /// Returns a buffer pointer containing the final elements of this buffer, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length") + let newCount = Swift.min(maxLength, count) + return extracting(Range(uncheckedBounds: (count - newCount, count))) + } +} % end ${visibility_boilerplate("end")} + diff --git a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb index 4d7cdde7a..295539310 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2022 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2022 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -16,6 +16,188 @@ ${autogenerated_warning()} % for modifier in visibility_levels: ${visibility_boilerplate(modifier)} +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + ${modifier} func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a buffer pointer containing the initial elements of this buffer, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// then the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + + /// Returns a buffer pointer containing the final elements of this buffer, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length") + let newCount = Swift.min(maxLength, count) + return extracting(Range(uncheckedBounds: (count - newCount, count))) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + ${modifier} func _moveInitializePrefix( + from source: UnsafeMutableBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + self.baseAddress.unsafelyUnwrapped.moveInitialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } +} + +extension UnsafeMutableBufferPointer { + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `source` must be fully initialized. + /// + /// The `source` buffer must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @inlinable + ${modifier} func _initializePrefix( + copying source: UnsafeBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } + + @inlinable + ${modifier} func _initializePrefix( + copying source: UnsafeMutableBufferPointer + ) -> Int { + _initializePrefix(copying: UnsafeBufferPointer(source)) + } + +#if compiler(>=6.2) + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The `source` span must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @available(SwiftStdlib 6.2, *) + @inlinable + ${modifier} func _initializePrefix(copying source: Span) -> Int { + source.withUnsafeBufferPointer { self._initializePrefix(copying: $0) } + } +#endif + +#if compiler(>=6.2) && FIXME + /// Initialize all slots in this buffer by copying data from `items`, which must fit entirely + /// in this buffer. + /// + /// If `items` contains more elements than can fit into this buffer, then this function + /// will return an index other than `items.endIndex`. In that case, `self` may not be fully + /// populated. + /// + /// If `Element` is not bitwise copyable, then this function must be called on an + /// entirely uninitialized buffer. + /// + /// - Returns: A pair of values `(count, end)`, where `count` is the number of items that were + /// successfully initialized, and `end` is the index into `items` after the last copied item. + @available(SwiftStdlib 6.2, *) + @inlinable + ${modifier} func _initializePrefix< + C: Container & ~Copyable & ~Escapable + >( + copying items: borrowing C + ) -> (copied: Int, end: C.Index) { + var target = self + var i = items.startIndex + while true { + let start = i + let span = items.span(after: &i) + if span.isEmpty { break } + guard span.count <= target.count else { + return (self.count - target.count, start) + } + target._initializeAndDropPrefix(copying: span) + } + return (self.count - target.count, i) + } +#endif + + /// Initialize slots at the start of this buffer by copying data from `buffer`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `buffer` must be fully initialized. + /// + /// The count of `buffer` must not be greater than `self.count`. + @inlinable + ${modifier} mutating func _initializeAndDropPrefix(copying source: UnsafeBufferPointer) { + let i = _initializePrefix(copying: source) + self = self.extracting(i...) + } + +#if compiler(>=6.2) + /// Initialize slots at the start of this buffer by copying data from `span`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The count of `span` must not be greater than `self.count`. + @available(SwiftStdlib 5.0, *) + @inlinable + ${modifier} mutating func _initializeAndDropPrefix(copying span: Span) { + span.withUnsafeBufferPointer { buffer in + self._initializeAndDropPrefix(copying: buffer) + } + } +#endif +} + extension UnsafeMutableBufferPointer { @inlinable ${modifier} func initialize(fromContentsOf source: Self) -> Index { diff --git a/Sources/InternalCollectionsUtilities/UnsafeMutableRawBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeMutableRawBufferPointer+Extras.swift.gyb new file mode 100644 index 000000000..8b649fbb0 --- /dev/null +++ b/Sources/InternalCollectionsUtilities/UnsafeMutableRawBufferPointer+Extras.swift.gyb @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +%{ + from gyb_utils import * +}% +${autogenerated_warning()} + +% for modifier in visibility_levels: +${visibility_boilerplate(modifier)} +extension UnsafeMutableRawBufferPointer { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableRawBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + ${modifier} func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} +% end +${visibility_boilerplate("end")} + diff --git a/Sources/InternalCollectionsUtilities/UnsafeRawBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeRawBufferPointer+Extras.swift.gyb new file mode 100644 index 000000000..dd3157f6e --- /dev/null +++ b/Sources/InternalCollectionsUtilities/UnsafeRawBufferPointer+Extras.swift.gyb @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +%{ + from gyb_utils import * +}% +${autogenerated_warning()} + +% for modifier in visibility_levels: +${visibility_boilerplate(modifier)} +extension UnsafeRawBufferPointer { + /// Returns a Boolean value indicating whether two `UnsafeRawBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + ${modifier} func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} +% end +${visibility_boilerplate("end")} + diff --git a/Sources/InternalCollectionsUtilities/autogenerated/LifetimeOverride.swift b/Sources/InternalCollectionsUtilities/autogenerated/LifetimeOverride.swift new file mode 100644 index 000000000..7c16eeec6 --- /dev/null +++ b/Sources/InternalCollectionsUtilities/autogenerated/LifetimeOverride.swift @@ -0,0 +1,169 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + + +// ############################################################################# +// # # +// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # +// # # +// ############################################################################# + + +#if compiler(>=6.2) + +// In single module mode, we need these declarations to be internal, +// but in regular builds we want them to be public. Unfortunately +// the current best way to do this is to duplicate all definitions. +#if COLLECTIONS_SINGLE_MODULE +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with an immortal lifetime. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(immortal) +internal func _unsafeImmortalize( + _ dependent: consuming T +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(borrow source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, borrowing source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` that inherits +/// all lifetime dependencies from the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(copy source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, copying source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's exclusive borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(&source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, + U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, + mutating source: inout U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} +#else // !COLLECTIONS_SINGLE_MODULE +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with an immortal lifetime. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(immortal) +public func _unsafeImmortalize( + _ dependent: consuming T +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(borrow source) +public func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, borrowing source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` that inherits +/// all lifetime dependencies from the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(copy source) +public func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, copying source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's exclusive borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@_lifetime(&source) +public func _overrideLifetime< + T: ~Copyable & ~Escapable, + U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, + mutating source: inout U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} +#endif // COLLECTIONS_SINGLE_MODULE +#endif diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift index 749f17bc8..014ddf270 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2021 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2021 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -30,6 +30,63 @@ extension UnsafeBufferPointer { return baseAddress.unsafelyUnwrapped + index } } + +extension UnsafeBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two `UnsafeBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + internal func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeBufferPointer where Element: ~Copyable { + /// Returns a buffer pointer containing the initial elements of this buffer, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// then the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + + /// Returns a buffer pointer containing the final elements of this buffer, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length") + let newCount = Swift.min(maxLength, count) + return extracting(Range(uncheckedBounds: (count - newCount, count))) + } +} #else // !COLLECTIONS_SINGLE_MODULE extension UnsafeBufferPointer { @inlinable @@ -39,4 +96,62 @@ extension UnsafeBufferPointer { return baseAddress.unsafelyUnwrapped + index } } + +extension UnsafeBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two `UnsafeBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + public func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeBufferPointer where Element: ~Copyable { + /// Returns a buffer pointer containing the initial elements of this buffer, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// then the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + + /// Returns a buffer pointer containing the final elements of this buffer, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// span from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length") + let newCount = Swift.min(maxLength, count) + return extracting(Range(uncheckedBounds: (count - newCount, count))) + } +} #endif // COLLECTIONS_SINGLE_MODULE + diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift index be578b238..568b9fed5 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2022 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2022 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -22,6 +22,188 @@ // but in regular builds we want them to be public. Unfortunately // the current best way to do this is to duplicate all definitions. #if COLLECTIONS_SINGLE_MODULE +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + internal func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a buffer pointer containing the initial elements of this buffer, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// then the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + + /// Returns a buffer pointer containing the final elements of this buffer, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length") + let newCount = Swift.min(maxLength, count) + return extracting(Range(uncheckedBounds: (count - newCount, count))) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + internal func _moveInitializePrefix( + from source: UnsafeMutableBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + self.baseAddress.unsafelyUnwrapped.moveInitialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } +} + +extension UnsafeMutableBufferPointer { + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `source` must be fully initialized. + /// + /// The `source` buffer must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @inlinable + internal func _initializePrefix( + copying source: UnsafeBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } + + @inlinable + internal func _initializePrefix( + copying source: UnsafeMutableBufferPointer + ) -> Int { + _initializePrefix(copying: UnsafeBufferPointer(source)) + } + +#if compiler(>=6.2) + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The `source` span must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @available(SwiftStdlib 6.2, *) + @inlinable + internal func _initializePrefix(copying source: Span) -> Int { + source.withUnsafeBufferPointer { self._initializePrefix(copying: $0) } + } +#endif + +#if compiler(>=6.2) && FIXME + /// Initialize all slots in this buffer by copying data from `items`, which must fit entirely + /// in this buffer. + /// + /// If `items` contains more elements than can fit into this buffer, then this function + /// will return an index other than `items.endIndex`. In that case, `self` may not be fully + /// populated. + /// + /// If `Element` is not bitwise copyable, then this function must be called on an + /// entirely uninitialized buffer. + /// + /// - Returns: A pair of values `(count, end)`, where `count` is the number of items that were + /// successfully initialized, and `end` is the index into `items` after the last copied item. + @available(SwiftStdlib 6.2, *) + @inlinable + internal func _initializePrefix< + C: Container & ~Copyable & ~Escapable + >( + copying items: borrowing C + ) -> (copied: Int, end: C.Index) { + var target = self + var i = items.startIndex + while true { + let start = i + let span = items.span(after: &i) + if span.isEmpty { break } + guard span.count <= target.count else { + return (self.count - target.count, start) + } + target._initializeAndDropPrefix(copying: span) + } + return (self.count - target.count, i) + } +#endif + + /// Initialize slots at the start of this buffer by copying data from `buffer`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `buffer` must be fully initialized. + /// + /// The count of `buffer` must not be greater than `self.count`. + @inlinable + internal mutating func _initializeAndDropPrefix(copying source: UnsafeBufferPointer) { + let i = _initializePrefix(copying: source) + self = self.extracting(i...) + } + +#if compiler(>=6.2) + /// Initialize slots at the start of this buffer by copying data from `span`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The count of `span` must not be greater than `self.count`. + @available(SwiftStdlib 5.0, *) + @inlinable + internal mutating func _initializeAndDropPrefix(copying span: Span) { + span.withUnsafeBufferPointer { buffer in + self._initializeAndDropPrefix(copying: buffer) + } + } +#endif +} + extension UnsafeMutableBufferPointer { @inlinable internal func initialize(fromContentsOf source: Self) -> Index { @@ -149,6 +331,188 @@ extension Slice { } } #else // !COLLECTIONS_SINGLE_MODULE +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + public func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a buffer pointer containing the initial elements of this buffer, + /// up to the specified maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// then the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(first maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a prefix of negative length") + let newCount = Swift.min(maxLength, count) + return Self(start: baseAddress, count: newCount) + } + + /// Returns a buffer pointer containing the final elements of this buffer, + /// up to the given maximum length. + /// + /// If the maximum length exceeds the length of this buffer pointer, + /// the result contains all the elements. + /// + /// The returned buffer's first item is always at offset 0; unlike buffer + /// slices, extracted buffers do not share their indices with the + /// buffer from which they are extracted. + /// + /// - Parameter maxLength: The maximum number of elements to return. + /// `maxLength` must be greater than or equal to zero. + /// - Returns: A buffer pointer with at most `maxLength` elements. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + @inline(__always) + public func _extracting(last maxLength: Int) -> Self { + precondition(maxLength >= 0, "Can't have a suffix of negative length") + let newCount = Swift.min(maxLength, count) + return extracting(Range(uncheckedBounds: (count - newCount, count))) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + public func _moveInitializePrefix( + from source: UnsafeMutableBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + self.baseAddress.unsafelyUnwrapped.moveInitialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } +} + +extension UnsafeMutableBufferPointer { + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `source` must be fully initialized. + /// + /// The `source` buffer must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @inlinable + public func _initializePrefix( + copying source: UnsafeBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } + + @inlinable + public func _initializePrefix( + copying source: UnsafeMutableBufferPointer + ) -> Int { + _initializePrefix(copying: UnsafeBufferPointer(source)) + } + +#if compiler(>=6.2) + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The `source` span must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @available(SwiftStdlib 6.2, *) + @inlinable + public func _initializePrefix(copying source: Span) -> Int { + source.withUnsafeBufferPointer { self._initializePrefix(copying: $0) } + } +#endif + +#if compiler(>=6.2) && FIXME + /// Initialize all slots in this buffer by copying data from `items`, which must fit entirely + /// in this buffer. + /// + /// If `items` contains more elements than can fit into this buffer, then this function + /// will return an index other than `items.endIndex`. In that case, `self` may not be fully + /// populated. + /// + /// If `Element` is not bitwise copyable, then this function must be called on an + /// entirely uninitialized buffer. + /// + /// - Returns: A pair of values `(count, end)`, where `count` is the number of items that were + /// successfully initialized, and `end` is the index into `items` after the last copied item. + @available(SwiftStdlib 6.2, *) + @inlinable + public func _initializePrefix< + C: Container & ~Copyable & ~Escapable + >( + copying items: borrowing C + ) -> (copied: Int, end: C.Index) { + var target = self + var i = items.startIndex + while true { + let start = i + let span = items.span(after: &i) + if span.isEmpty { break } + guard span.count <= target.count else { + return (self.count - target.count, start) + } + target._initializeAndDropPrefix(copying: span) + } + return (self.count - target.count, i) + } +#endif + + /// Initialize slots at the start of this buffer by copying data from `buffer`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `buffer` must be fully initialized. + /// + /// The count of `buffer` must not be greater than `self.count`. + @inlinable + public mutating func _initializeAndDropPrefix(copying source: UnsafeBufferPointer) { + let i = _initializePrefix(copying: source) + self = self.extracting(i...) + } + +#if compiler(>=6.2) + /// Initialize slots at the start of this buffer by copying data from `span`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The count of `span` must not be greater than `self.count`. + @available(SwiftStdlib 5.0, *) + @inlinable + public mutating func _initializeAndDropPrefix(copying span: Span) { + span.withUnsafeBufferPointer { buffer in + self._initializeAndDropPrefix(copying: buffer) + } + } +#endif +} + extension UnsafeMutableBufferPointer { @inlinable public func initialize(fromContentsOf source: Self) -> Index { diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableRawBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableRawBufferPointer+Extras.swift new file mode 100644 index 000000000..b97bfe3bf --- /dev/null +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableRawBufferPointer+Extras.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + + +// ############################################################################# +// # # +// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # +// # # +// ############################################################################# + + + +// In single module mode, we need these declarations to be internal, +// but in regular builds we want them to be public. Unfortunately +// the current best way to do this is to duplicate all definitions. +#if COLLECTIONS_SINGLE_MODULE +extension UnsafeMutableRawBufferPointer { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableRawBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + internal func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} +#else // !COLLECTIONS_SINGLE_MODULE +extension UnsafeMutableRawBufferPointer { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableRawBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + public func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} +#endif // COLLECTIONS_SINGLE_MODULE + diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeRawBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeRawBufferPointer+Extras.swift new file mode 100644 index 000000000..7caa59d08 --- /dev/null +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeRawBufferPointer+Extras.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + + +// ############################################################################# +// # # +// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # +// # # +// ############################################################################# + + + +// In single module mode, we need these declarations to be internal, +// but in regular builds we want them to be public. Unfortunately +// the current best way to do this is to duplicate all definitions. +#if COLLECTIONS_SINGLE_MODULE +extension UnsafeRawBufferPointer { + /// Returns a Boolean value indicating whether two `UnsafeRawBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + internal func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} +#else // !COLLECTIONS_SINGLE_MODULE +extension UnsafeRawBufferPointer { + /// Returns a Boolean value indicating whether two `UnsafeRawBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + public func _isIdentical(to other: Self) -> Bool { + (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} +#endif // COLLECTIONS_SINGLE_MODULE + diff --git a/Sources/OrderedCollections/OrderedSet/OrderedSet+Diffing.swift b/Sources/OrderedCollections/OrderedSet/OrderedSet+Diffing.swift index 008d2664e..f3dd22b33 100644 --- a/Sources/OrderedCollections/OrderedSet/OrderedSet+Diffing.swift +++ b/Sources/OrderedCollections/OrderedSet/OrderedSet+Diffing.swift @@ -15,7 +15,7 @@ extension OrderedSet { /// testing and the member uniqueness guarantees of `OrderedSet`. /// /// - Complexity: O(`self.count + other.count`) - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @available(SwiftStdlib 5.1, *) public func difference( from other: Self ) -> CollectionDifference { @@ -91,7 +91,7 @@ extension OrderedSet { /// /// - Complexity: O(*n* + *c*), where *n* is `self.count` and *c* /// is the number of changes contained by the parameter. - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @available(SwiftStdlib 5.1, *) public func applying(_ difference: CollectionDifference) -> Self? { guard let array = self.elements.applying(difference) else { return nil } let result = OrderedSet(array) diff --git a/Sources/RopeModule/BigString/Basics/BigString+Builder.swift b/Sources/RopeModule/BigString/Basics/BigString+Builder.swift index d5b4f089a..a2b60d4f0 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Builder.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Builder.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { struct Builder { typealias _Chunk = BigString._Chunk @@ -38,7 +38,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension Rope.Builder { internal func _breakState() -> _CharacterRecognizer { let chars = self.prefixSummary.characters @@ -57,7 +57,7 @@ extension Rope.Builder { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Builder { mutating func append(_ str: __owned some StringProtocol) { append(Substring(str)) @@ -129,7 +129,7 @@ extension BigString.Builder { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Builder { mutating func finalize() -> BigString { // Resync breaks in suffix. diff --git a/Sources/RopeModule/BigString/Basics/BigString+Contents.swift b/Sources/RopeModule/BigString/Basics/BigString+Contents.swift index ff3029a32..abc57d983 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Contents.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Contents.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { /// The estimated maximum number of UTF-8 code units that `BigString` is guaranteed to be able /// to hold without encountering an overflow in its operations. This corresponds to the capacity @@ -31,7 +31,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { var _characterCount: Int { _rope.summary.characters } var _unicodeScalarCount: Int { _rope.summary.unicodeScalars } @@ -39,7 +39,7 @@ extension BigString { var _utf8Count: Int { _rope.summary.utf8 } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _distance( from start: Index, @@ -91,7 +91,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { // FIXME: See if we need direct implementations for these. @@ -113,7 +113,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { // FIXME: See if we need direct implementations for these. @@ -134,7 +134,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _index( _ i: Index, @@ -201,7 +201,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _index( _ i: Index, @@ -249,7 +249,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _characterIndex(after i: Index) -> Index { let i = _characterIndex(roundingDown: i) @@ -281,7 +281,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _characterIndex(before i: Index) -> Index { let i = _characterIndex(roundingDown: i) @@ -316,7 +316,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _characterIndex(roundingDown i: Index) -> Index { let offset = i.utf8Offset @@ -390,7 +390,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _characterIndex(roundingUp i: Index) -> Index { let j = _characterIndex(roundingDown: i) @@ -421,7 +421,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _character(at start: Index) -> (character: Character, end: Index) { let start = _characterIndex(roundingDown: start) @@ -504,7 +504,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _foreachChunk( from start: Index, diff --git a/Sources/RopeModule/BigString/Basics/BigString+Debugging.swift b/Sources/RopeModule/BigString/Basics/BigString+Debugging.swift index ed8316e3f..5ed3dbf2f 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Debugging.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Debugging.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public func _dump(heightLimit: Int = .max) { _rope._dump(heightLimit: heightLimit) diff --git a/Sources/RopeModule/BigString/Basics/BigString+Index.swift b/Sources/RopeModule/BigString/Basics/BigString+Index.swift index dd922ed7e..e2a598934 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Index.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Index.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public struct Index: Sendable { typealias _Rope = BigString._Rope @@ -38,7 +38,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Index { @inline(__always) internal static func _bitsForUTF8Offset(_ utf8Offset: Int) -> UInt64 { @@ -93,7 +93,7 @@ extension BigString.Index { } extension String.Index { - @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) + @available(SwiftStdlib 5.8, *) func _copyingAlignmentBits(from i: BigString.Index) -> String.Index { var bits = _abi_rawBits & ~3 bits |= (i._flags &>> 8) & 3 @@ -101,7 +101,7 @@ extension String.Index { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Index { internal var _chunkIndex: String.Index { assert(_rope != nil) @@ -111,7 +111,7 @@ extension BigString.Index { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Index { internal mutating func _clearUTF16TrailingSurrogate() { _flags = 0 @@ -176,28 +176,28 @@ extension BigString.Index { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Index: Equatable { public static func ==(left: Self, right: Self) -> Bool { left._orderingValue == right._orderingValue } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Index: Comparable { public static func <(left: Self, right: Self) -> Bool { left._orderingValue < right._orderingValue } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Index: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(_orderingValue) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Index: CustomStringConvertible { public var description: String { let utf16Offset = _isUTF16TrailingSurrogate ? "+1" : "" @@ -205,7 +205,7 @@ extension BigString.Index: CustomStringConvertible { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func resolve(_ i: Index, preferEnd: Bool) -> Index { if var ri = i._rope, _rope.isValid(ri) { diff --git a/Sources/RopeModule/BigString/Basics/BigString+Ingester.swift b/Sources/RopeModule/BigString/Basics/BigString+Ingester.swift index 1ef926aa5..0437ecba5 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Ingester.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Ingester.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _ingester( forInserting input: __owned Substring, @@ -22,7 +22,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { internal struct _Ingester { typealias _Chunk = BigString._Chunk @@ -141,7 +141,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension String { func _nextSlice( after i: Index, @@ -157,7 +157,7 @@ extension String { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { init(_ string: String) { guard !string.isEmpty else { self.init(); return } diff --git a/Sources/RopeModule/BigString/Basics/BigString+Invariants.swift b/Sources/RopeModule/BigString/Basics/BigString+Invariants.swift index b455b9783..74b506fa1 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Invariants.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Invariants.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public func _invariantCheck() { #if COLLECTIONS_INTERNAL_CHECKS diff --git a/Sources/RopeModule/BigString/Basics/BigString+Iterators.swift b/Sources/RopeModule/BigString/Basics/BigString+Iterators.swift index 94484e722..64ee7cdce 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Iterators.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Iterators.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { struct ChunkIterator { var base: _Rope.Iterator @@ -24,7 +24,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.ChunkIterator: IteratorProtocol { typealias Element = String diff --git a/Sources/RopeModule/BigString/Basics/BigString+Metrics.swift b/Sources/RopeModule/BigString/Basics/BigString+Metrics.swift index 42775e9c6..fab9b6fe1 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Metrics.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Metrics.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) internal protocol _StringMetric: RopeMetric where Element == BigString._Chunk { /// Measure the distance between the given start and end positions within /// the specified chunk. @@ -47,7 +47,7 @@ internal protocol _StringMetric: RopeMetric where Element == BigString._Chunk { ) -> (found: Bool, forward: Bool) } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { internal struct _CharacterMetric: _StringMetric { typealias Element = BigString._Chunk diff --git a/Sources/RopeModule/BigString/Basics/BigString+Summary.swift b/Sources/RopeModule/BigString/Basics/BigString+Summary.swift index aeac62064..ca44f3ab3 100644 --- a/Sources/RopeModule/BigString/Basics/BigString+Summary.swift +++ b/Sources/RopeModule/BigString/Basics/BigString+Summary.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { struct Summary { // FIXME: We only need 48 * 3 = 192 bits to represent a nonnegative value; pack these better @@ -35,14 +35,14 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Summary: CustomStringConvertible { var description: String { "❨\(utf8)⋅\(utf16)⋅\(unicodeScalars)⋅\(characters)❩" } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Summary: RopeSummary { @inline(__always) static var maxNodeSize: Int { diff --git a/Sources/RopeModule/BigString/Basics/BigString.swift b/Sources/RopeModule/BigString/Basics/BigString.swift index 220c76318..9971cf6f6 100644 --- a/Sources/RopeModule/BigString/Basics/BigString.swift +++ b/Sources/RopeModule/BigString/Basics/BigString.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// /// The core of a B-tree based String implementation. -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) public struct BigString: Sendable { typealias _Rope = Rope<_Chunk> diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Append and Insert.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Append and Insert.swift index f72dd07a6..c8e401e93 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Append and Insert.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Append and Insert.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { mutating func append(_ other: __owned Self) { self._append(other.string[...], other.counts) @@ -64,7 +64,7 @@ extension BigString._Chunk { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { mutating func _insert( _ slice: Slice, diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Breaks.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Breaks.swift index bf66b2069..4889dd06a 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Breaks.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Breaks.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { @inline(__always) var hasBreaks: Bool { counts.hasBreaks } @@ -38,7 +38,7 @@ extension BigString._Chunk { var wholeCharacters: Substring { string[firstBreak...] } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { var immediateLastBreakState: _CharacterRecognizer? { guard hasBreaks else { return nil } diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Counts.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Counts.swift index f0354d9be..8ae266971 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Counts.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Counts.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { struct Counts: Equatable { /// The number of UTF-8 code units within this chunk. @@ -97,7 +97,7 @@ extension BigString._Chunk { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk.Counts { var characters: Int { get { Int(_characters) } @@ -123,7 +123,7 @@ extension BigString._Chunk.Counts { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk.Counts { mutating func append(_ other: Self) { assert(hasSpaceToMerge(other)) diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Description.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Description.swift index d22dff934..c2f9d5cfa 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Description.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Description.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk: CustomStringConvertible { var description: String { let counts = """ diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Indexing by Characters.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Indexing by Characters.swift index 4371a2599..4f073ad3c 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Indexing by Characters.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Indexing by Characters.swift @@ -9,14 +9,14 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension UInt8 { /// Returns true if this is a leading code unit in the UTF-8 encoding of a Unicode scalar that /// is outside the BMP. var _isUTF8NonBMPLeadingCodeUnit: Bool { self >= 0b11110000 } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { func characterDistance( from start: String.Index, diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Indexing by UTF16.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Indexing by UTF16.swift index 916a89d09..2e3990f3f 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Indexing by UTF16.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Indexing by UTF16.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { /// UTF-16 index lookup. func index(at utf8Offset: Int, utf16TrailingSurrogate: Bool) -> String.Index { diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+RopeElement.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+RopeElement.swift index 668c127e1..b9d50d4c6 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+RopeElement.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+RopeElement.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk: RopeElement { typealias Summary = BigString.Summary typealias Index = String.Index @@ -114,7 +114,7 @@ extension BigString._Chunk: RopeElement { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { static func _redistributeData( _ left: inout Self, diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Splitting.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Splitting.swift index e315ddab9..b41eae01a 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Splitting.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk+Splitting.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { func splitCounts(at i: String.Index) -> (left: Counts, right: Counts) { precondition(i <= string.endIndex) diff --git a/Sources/RopeModule/BigString/Chunk/BigString+Chunk.swift b/Sources/RopeModule/BigString/Chunk/BigString+Chunk.swift index 133951191..d42eec8c1 100644 --- a/Sources/RopeModule/BigString/Chunk/BigString+Chunk.swift +++ b/Sources/RopeModule/BigString/Chunk/BigString+Chunk.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { internal struct _Chunk { typealias Slice = (string: Substring, characters: Int, prefix: Int, suffix: Int) @@ -44,7 +44,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { @inline(__always) static var maxUTF8Count: Int { 255 } @@ -56,7 +56,7 @@ extension BigString._Chunk { static var maxSlicingError: Int { 3 } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { @inline(__always) mutating func take() -> Self { @@ -73,7 +73,7 @@ extension BigString._Chunk { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { @inline(__always) var characterCount: Int { counts.characters } @@ -97,7 +97,7 @@ extension BigString._Chunk { var lastScalar: UnicodeScalar { string.unicodeScalars.last! } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { var availableSpace: Int { Swift.max(0, Self.maxUTF8Count - utf8Count) } @@ -107,7 +107,7 @@ extension BigString._Chunk { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { func hasSpaceToMerge(_ other: some StringProtocol) -> Bool { utf8Count + other.utf8.count <= Self.maxUTF8Count diff --git a/Sources/RopeModule/BigString/Conformances/BigString+BidirectionalCollection.swift b/Sources/RopeModule/BigString/Conformances/BigString+BidirectionalCollection.swift index 4dd092c18..e6682962c 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+BidirectionalCollection.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+BidirectionalCollection.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: BidirectionalCollection { public typealias SubSequence = BigSubstring @@ -59,7 +59,7 @@ extension BigString: BidirectionalCollection { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public func index(roundingDown i: Index) -> Index { _characterIndex(roundingDown: i) diff --git a/Sources/RopeModule/BigString/Conformances/BigString+Comparable.swift b/Sources/RopeModule/BigString/Conformances/BigString+Comparable.swift index 07ec6d1c9..c8cd0d177 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+Comparable.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+Comparable.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: Comparable { public static func < (left: Self, right: Self) -> Bool { // FIXME: Implement properly normalized comparisons & hashing. @@ -34,7 +34,7 @@ extension BigString: Comparable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { /// Lexicographically compare the UTF-8 representations of `left` to `right`, returning a Boolean /// value indicating whether `left` is ordered before `right`. diff --git a/Sources/RopeModule/BigString/Conformances/BigString+CustomDebugStringConvertible.swift b/Sources/RopeModule/BigString/Conformances/BigString+CustomDebugStringConvertible.swift index 5ea0d2035..31dfc6e32 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+CustomDebugStringConvertible.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+CustomDebugStringConvertible.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: CustomDebugStringConvertible { public var debugDescription: String { description.debugDescription diff --git a/Sources/RopeModule/BigString/Conformances/BigString+CustomStringConvertible.swift b/Sources/RopeModule/BigString/Conformances/BigString+CustomStringConvertible.swift index 5fd0007f8..c0f047e17 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+CustomStringConvertible.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+CustomStringConvertible.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: CustomStringConvertible { public var description: String { String(self) diff --git a/Sources/RopeModule/BigString/Conformances/BigString+Equatable.swift b/Sources/RopeModule/BigString/Conformances/BigString+Equatable.swift index 95f47fd04..c445b9aa8 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+Equatable.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+Equatable.swift @@ -9,14 +9,14 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public func isIdentical(to other: Self) -> Bool { self._rope.isIdentical(to: other._rope) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: Equatable { public static func ==(left: Self, right: Self) -> Bool { // FIXME: Implement properly normalized comparisons & hashing. @@ -40,7 +40,7 @@ extension BigString: Equatable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { /// Lexicographically compare the UTF-8 representations of `left` to `right`, returning a Boolean /// value indicating whether `left` is equal to `right`. diff --git a/Sources/RopeModule/BigString/Conformances/BigString+ExpressibleByStringLiteral.swift b/Sources/RopeModule/BigString/Conformances/BigString+ExpressibleByStringLiteral.swift index 8ee2e95cd..c441b60b8 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+ExpressibleByStringLiteral.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+ExpressibleByStringLiteral.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(value) diff --git a/Sources/RopeModule/BigString/Conformances/BigString+Hashing.swift b/Sources/RopeModule/BigString/Conformances/BigString+Hashing.swift index baacc3825..74c703075 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+Hashing.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+Hashing.swift @@ -9,14 +9,14 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: Hashable { public func hash(into hasher: inout Hasher) { hashCharacters(into: &hasher) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { internal func hashCharacters(into hasher: inout Hasher) { // FIXME: Implement properly normalized comparisons & hashing. diff --git a/Sources/RopeModule/BigString/Conformances/BigString+LosslessStringConvertible.swift b/Sources/RopeModule/BigString/Conformances/BigString+LosslessStringConvertible.swift index 0133be295..50e82e4db 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+LosslessStringConvertible.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+LosslessStringConvertible.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: LosslessStringConvertible { // init?(_: String) is implemented by RangeReplaceableCollection.init(_:) } diff --git a/Sources/RopeModule/BigString/Conformances/BigString+RangeReplaceableCollection.swift b/Sources/RopeModule/BigString/Conformances/BigString+RangeReplaceableCollection.swift index 653cb6a00..7d6856659 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+RangeReplaceableCollection.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+RangeReplaceableCollection.swift @@ -13,7 +13,7 @@ import InternalCollectionsUtilities #endif -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: RangeReplaceableCollection { public init() { self.init(_rope: _Rope()) diff --git a/Sources/RopeModule/BigString/Conformances/BigString+Sequence.swift b/Sources/RopeModule/BigString/Conformances/BigString+Sequence.swift index 8aa334e4b..fe89daef4 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+Sequence.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+Sequence.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: Sequence { public typealias Element = Character @@ -18,7 +18,7 @@ extension BigString: Sequence { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public struct Iterator { internal let _base: BigString @@ -66,7 +66,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Iterator: IteratorProtocol { public typealias Element = Character @@ -158,7 +158,7 @@ extension BigString.Iterator: IteratorProtocol { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.Iterator { // The UTF-8 offset of the current position, from the start of the string. var utf8Offset: Int { diff --git a/Sources/RopeModule/BigString/Conformances/BigString+TextOutputStream.swift b/Sources/RopeModule/BigString/Conformances/BigString+TextOutputStream.swift index 33c847f3a..bdef67d9a 100644 --- a/Sources/RopeModule/BigString/Conformances/BigString+TextOutputStream.swift +++ b/Sources/RopeModule/BigString/Conformances/BigString+TextOutputStream.swift @@ -9,14 +9,14 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: TextOutputStream { public mutating func write(_ string: String) { append(contentsOf: string) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString: TextOutputStreamable { public func write(to target: inout some TextOutputStream) { for chunk in _rope { diff --git a/Sources/RopeModule/BigString/Operations/BigString+Append.swift b/Sources/RopeModule/BigString/Operations/BigString+Append.swift index 2e57db961..7519e29b1 100644 --- a/Sources/RopeModule/BigString/Operations/BigString+Append.swift +++ b/Sources/RopeModule/BigString/Operations/BigString+Append.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { mutating func _append(contentsOf other: __owned Substring) { if other.isEmpty { return } @@ -39,7 +39,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { var _firstUnicodeScalar: Unicode.Scalar { assert(!isEmpty) @@ -104,14 +104,14 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { var isUndersized: Bool { _utf8Count < _Chunk.minUTF8Count } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { /// Note: This assumes `other` already has the correct break positions. mutating func _append(_ other: __owned _Chunk) { diff --git a/Sources/RopeModule/BigString/Operations/BigString+Initializers.swift b/Sources/RopeModule/BigString/Operations/BigString+Initializers.swift index 41477c41a..32b8c64c9 100644 --- a/Sources/RopeModule/BigString/Operations/BigString+Initializers.swift +++ b/Sources/RopeModule/BigString/Operations/BigString+Initializers.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { internal init(_from input: some StringProtocol) { var builder = _Rope.Builder() @@ -84,7 +84,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension String { public init(_ big: BigString) { guard !big.isEmpty else { diff --git a/Sources/RopeModule/BigString/Operations/BigString+Insert.swift b/Sources/RopeModule/BigString/Operations/BigString+Insert.swift index 14911c9dd..8c4adeb5b 100644 --- a/Sources/RopeModule/BigString/Operations/BigString+Insert.swift +++ b/Sources/RopeModule/BigString/Operations/BigString+Insert.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { mutating func _insert( contentsOf other: __owned Substring, @@ -52,7 +52,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { mutating func _insert(contentsOf other: __owned Self, at index: Index) { guard index < endIndex else { @@ -76,7 +76,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { mutating func _insert(contentsOf other: __owned Self, in range: Range, at index: Index) { guard index < endIndex else { diff --git a/Sources/RopeModule/BigString/Operations/BigString+Managing Breaks.swift b/Sources/RopeModule/BigString/Operations/BigString+Managing Breaks.swift index 9ad24dd33..491806f41 100644 --- a/Sources/RopeModule/BigString/Operations/BigString+Managing Breaks.swift +++ b/Sources/RopeModule/BigString/Operations/BigString+Managing Breaks.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { func _breakState( upTo index: Index, @@ -59,7 +59,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { /// - Returns: the position at which the grapheme breaks finally sync up with the originals. /// (or nil if they never did). @@ -97,7 +97,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Rope { mutating func resyncBreaks( old: inout _CharacterRecognizer, @@ -155,7 +155,7 @@ extension BigString._Rope { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString._Chunk { /// Resyncronize chunk metadata with the (possibly) reshuffled grapheme /// breaks after an insertion that ended at `index`. diff --git a/Sources/RopeModule/BigString/Operations/BigString+RemoveSubrange.swift b/Sources/RopeModule/BigString/Operations/BigString+RemoveSubrange.swift index 68936c2fb..61cdfff3b 100644 --- a/Sources/RopeModule/BigString/Operations/BigString+RemoveSubrange.swift +++ b/Sources/RopeModule/BigString/Operations/BigString+RemoveSubrange.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { mutating func _removeSubrange(_ bounds: Range) { precondition(bounds.upperBound <= endIndex, "Index out of bounds") @@ -20,7 +20,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { mutating func removeCharacter(at i: Index) -> Character { let start = self.resolve(i, preferEnd: false) diff --git a/Sources/RopeModule/BigString/Operations/BigString+ReplaceSubrange.swift b/Sources/RopeModule/BigString/Operations/BigString+ReplaceSubrange.swift index 3e14bf436..5ca847930 100644 --- a/Sources/RopeModule/BigString/Operations/BigString+ReplaceSubrange.swift +++ b/Sources/RopeModule/BigString/Operations/BigString+ReplaceSubrange.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { mutating func _replaceSubrange( _ range: Range, diff --git a/Sources/RopeModule/BigString/Operations/BigString+Split.swift b/Sources/RopeModule/BigString/Operations/BigString+Split.swift index a8bad19ca..66ac909a5 100644 --- a/Sources/RopeModule/BigString/Operations/BigString+Split.swift +++ b/Sources/RopeModule/BigString/Operations/BigString+Split.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { mutating func split( at index: Index diff --git a/Sources/RopeModule/BigString/Operations/Range+BigString.swift b/Sources/RopeModule/BigString/Operations/Range+BigString.swift index e784f2f2f..d5745a6f8 100644 --- a/Sources/RopeModule/BigString/Operations/Range+BigString.swift +++ b/Sources/RopeModule/BigString/Operations/Range+BigString.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension Range { internal var _isEmptyUTF8: Bool { lowerBound.utf8Offset == upperBound.utf8Offset diff --git a/Sources/RopeModule/BigString/Views/BigString+UTF16View.swift b/Sources/RopeModule/BigString/Views/BigString+UTF16View.swift index da9d98239..f7bb62ba0 100644 --- a/Sources/RopeModule/BigString/Views/BigString+UTF16View.swift +++ b/Sources/RopeModule/BigString/Views/BigString+UTF16View.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public struct UTF16View: Sendable { var _base: BigString @@ -30,7 +30,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF16View: Equatable { public static func ==(left: Self, right: Self) -> Bool { BigString.utf8IsEqual(left._base, to: right._base) @@ -41,14 +41,14 @@ extension BigString.UTF16View: Equatable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF16View: Hashable { public func hash(into hasher: inout Hasher) { _base.hashUTF8(into: &hasher) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF16View: Sequence { public typealias Element = UInt16 @@ -67,7 +67,7 @@ extension BigString.UTF16View: Sequence { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF16View.Iterator: IteratorProtocol { public typealias Element = UInt16 @@ -91,7 +91,7 @@ extension BigString.UTF16View.Iterator: IteratorProtocol { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF16View: BidirectionalCollection { public typealias Index = BigString.Index public typealias SubSequence = BigSubstring.UTF16View @@ -136,7 +136,7 @@ extension BigString.UTF16View: BidirectionalCollection { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF16View { public func index(roundingDown i: Index) -> Index { _base._utf16Index(roundingDown: i) diff --git a/Sources/RopeModule/BigString/Views/BigString+UTF8View.swift b/Sources/RopeModule/BigString/Views/BigString+UTF8View.swift index d751b3e44..b60815208 100644 --- a/Sources/RopeModule/BigString/Views/BigString+UTF8View.swift +++ b/Sources/RopeModule/BigString/Views/BigString+UTF8View.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public struct UTF8View: Sendable { var _base: BigString @@ -30,7 +30,7 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF8View: Equatable { public static func ==(left: Self, right: Self) -> Bool { BigString.utf8IsEqual(left._base, to: right._base) @@ -41,14 +41,14 @@ extension BigString.UTF8View: Equatable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF8View: Hashable { public func hash(into hasher: inout Hasher) { _base.hashUTF8(into: &hasher) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF8View: Sequence { public typealias Element = UInt8 @@ -67,7 +67,7 @@ extension BigString.UTF8View: Sequence { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF8View.Iterator: IteratorProtocol { public typealias Element = UInt8 @@ -127,7 +127,7 @@ extension BigString.UTF8View.Iterator: IteratorProtocol { -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF8View: BidirectionalCollection { public typealias Index = BigString.Index public typealias SubSequence = BigSubstring.UTF8View @@ -172,7 +172,7 @@ extension BigString.UTF8View: BidirectionalCollection { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UTF8View { public func index(roundingDown i: Index) -> Index { _base._utf8Index(roundingDown: i) diff --git a/Sources/RopeModule/BigString/Views/BigString+UnicodeScalarView.swift b/Sources/RopeModule/BigString/Views/BigString+UnicodeScalarView.swift index 17d382a24..bea760e5f 100644 --- a/Sources/RopeModule/BigString/Views/BigString+UnicodeScalarView.swift +++ b/Sources/RopeModule/BigString/Views/BigString+UnicodeScalarView.swift @@ -13,7 +13,7 @@ import InternalCollectionsUtilities #endif -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public struct UnicodeScalarView: Sendable { var _base: BigString @@ -47,28 +47,28 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(value.unicodeScalars) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView: CustomStringConvertible { public var description: String { String(_base) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView: CustomDebugStringConvertible { public var debugDescription: String { description.debugDescription } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView: Equatable { public static func ==(left: Self, right: Self) -> Bool { BigString.utf8IsEqual(left._base, to: right._base) @@ -79,14 +79,14 @@ extension BigString.UnicodeScalarView: Equatable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView: Hashable { public func hash(into hasher: inout Hasher) { _base.hashUTF8(into: &hasher) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView: Sequence { public typealias Element = UnicodeScalar @@ -105,7 +105,7 @@ extension BigString.UnicodeScalarView: Sequence { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView.Iterator: IteratorProtocol { public typealias Element = UnicodeScalar @@ -129,7 +129,7 @@ extension BigString.UnicodeScalarView.Iterator: IteratorProtocol { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView: BidirectionalCollection { public typealias Index = BigString.Index public typealias SubSequence = BigSubstring.UnicodeScalarView @@ -174,7 +174,7 @@ extension BigString.UnicodeScalarView: BidirectionalCollection { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView { public func index(roundingDown i: Index) -> Index { _base._unicodeScalarIndex(roundingDown: i) @@ -185,7 +185,7 @@ extension BigString.UnicodeScalarView { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString.UnicodeScalarView: RangeReplaceableCollection { public init() { self._base = BigString() diff --git a/Sources/RopeModule/BigString/Views/BigSubstring+UTF16View.swift b/Sources/RopeModule/BigString/Views/BigSubstring+UTF16View.swift index c1af2c469..057725fdc 100644 --- a/Sources/RopeModule/BigString/Views/BigSubstring+UTF16View.swift +++ b/Sources/RopeModule/BigString/Views/BigSubstring+UTF16View.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring { public struct UTF16View: Sendable { internal var _base: BigString @@ -37,7 +37,7 @@ extension BigSubstring { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public init?(_ utf16: BigSubstring.UTF16View) { guard @@ -50,12 +50,12 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF16View { public var base: BigString.UTF16View { _base.utf16 } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF16View: Equatable { public static func ==(left: Self, right: Self) -> Bool { var i1 = left._bounds.lowerBound @@ -90,7 +90,7 @@ extension BigSubstring.UTF16View: Equatable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF16View: Hashable { public func hash(into hasher: inout Hasher) { for codeUnit in self { @@ -100,7 +100,7 @@ extension BigSubstring.UTF16View: Hashable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF16View: Sequence { public typealias Element = UInt16 @@ -124,7 +124,7 @@ extension BigSubstring.UTF16View: Sequence { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF16View: BidirectionalCollection { public typealias Index = BigString.Index public typealias SubSequence = Self @@ -185,7 +185,7 @@ extension BigSubstring.UTF16View: BidirectionalCollection { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF16View { public func index(roundingDown i: Index) -> Index { precondition(i >= startIndex && i <= endIndex, "Index out of bounds") diff --git a/Sources/RopeModule/BigString/Views/BigSubstring+UTF8View.swift b/Sources/RopeModule/BigString/Views/BigSubstring+UTF8View.swift index c2bb610c9..7919c682f 100644 --- a/Sources/RopeModule/BigString/Views/BigSubstring+UTF8View.swift +++ b/Sources/RopeModule/BigString/Views/BigSubstring+UTF8View.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring { public struct UTF8View: Sendable { internal var _base: BigString @@ -37,7 +37,7 @@ extension BigSubstring { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public init?(_ utf8: BigSubstring.UTF8View) { guard @@ -50,12 +50,12 @@ extension BigString { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF8View { public var base: BigString.UTF8View { _base.utf8 } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF8View: Equatable { public static func ==(left: Self, right: Self) -> Bool { BigString.utf8IsEqual(left._base, in: left._bounds, to: right._base, in: right._bounds) @@ -67,14 +67,14 @@ extension BigSubstring.UTF8View: Equatable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF8View: Hashable { public func hash(into hasher: inout Hasher) { _base.hashUTF8(into: &hasher, from: _bounds.lowerBound, to: _bounds.upperBound) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF8View: Sequence { public typealias Element = UInt8 @@ -98,7 +98,7 @@ extension BigSubstring.UTF8View: Sequence { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF8View: BidirectionalCollection { public typealias Index = BigString.Index public typealias SubSequence = Self @@ -159,7 +159,7 @@ extension BigSubstring.UTF8View: BidirectionalCollection { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UTF8View { public func index(roundingDown i: Index) -> Index { precondition(i >= startIndex && i <= endIndex, "Index out of bounds") diff --git a/Sources/RopeModule/BigString/Views/BigSubstring+UnicodeScalarView.swift b/Sources/RopeModule/BigString/Views/BigSubstring+UnicodeScalarView.swift index 49ab26566..266c1acff 100644 --- a/Sources/RopeModule/BigString/Views/BigSubstring+UnicodeScalarView.swift +++ b/Sources/RopeModule/BigString/Views/BigSubstring+UnicodeScalarView.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring { public struct UnicodeScalarView: Sendable { internal var _base: BigString @@ -52,40 +52,40 @@ extension BigSubstring { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigString { public init(_ unicodeScalars: BigSubstring.UnicodeScalarView) { self.init(_from: unicodeScalars._base, in: unicodeScalars._bounds) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView { public var base: BigString.UnicodeScalarView { _base.unicodeScalars } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(value.unicodeScalars) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView: CustomStringConvertible { public var description: String { String(_from: _base, in: _bounds) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView: CustomDebugStringConvertible { public var debugDescription: String { description.debugDescription } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView: Equatable { public static func ==(left: Self, right: Self) -> Bool { BigString.utf8IsEqual(left._base, in: left._bounds, to: right._base, in: right._bounds) @@ -97,14 +97,14 @@ extension BigSubstring.UnicodeScalarView: Equatable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView: Hashable { public func hash(into hasher: inout Hasher) { _base.hashUTF8(into: &hasher, from: _bounds.lowerBound, to: _bounds.upperBound) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView: Sequence { public typealias Element = UnicodeScalar @@ -128,7 +128,7 @@ extension BigSubstring.UnicodeScalarView: Sequence { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView: BidirectionalCollection { public typealias Index = BigString.Index public typealias SubSequence = Self @@ -191,7 +191,7 @@ extension BigSubstring.UnicodeScalarView: BidirectionalCollection { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView { public func index(roundingDown i: Index) -> Index { precondition(i >= startIndex && i <= endIndex, "Index out of bounds") @@ -204,7 +204,7 @@ extension BigSubstring.UnicodeScalarView { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView { /// Run the closure `body` to mutate the contents of this view within `range`, then update /// the bounds of this view to maintain their logical position in the resulting string. @@ -237,7 +237,7 @@ extension BigSubstring.UnicodeScalarView { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring.UnicodeScalarView: RangeReplaceableCollection { public init() { self.init(_substring: BigSubstring()) diff --git a/Sources/RopeModule/BigString/Views/BigSubstring.swift b/Sources/RopeModule/BigString/Views/BigSubstring.swift index ee9a40c12..3ad56916c 100644 --- a/Sources/RopeModule/BigString/Views/BigSubstring.swift +++ b/Sources/RopeModule/BigString/Views/BigSubstring.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) public struct BigSubstring: Sendable { var _base: BigString var _bounds: Range @@ -32,12 +32,12 @@ public struct BigSubstring: Sendable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring { public var base: BigString { _base } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring { func _foreachChunk( _ body: (Substring) -> Void @@ -46,33 +46,33 @@ extension BigSubstring { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: CustomStringConvertible { public var description: String { String(_from: _base, in: _bounds) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: CustomDebugStringConvertible { public var debugDescription: String { description.debugDescription } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(value) } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: LosslessStringConvertible { // init?(_: String) is implemented by RangeReplaceableCollection.init(_:) } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: Equatable { public static func ==(left: Self, right: Self) -> Bool { // FIXME: Implement properly normalized comparisons & hashing. @@ -103,7 +103,7 @@ extension BigSubstring: Equatable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: Hashable { public func hash(into hasher: inout Hasher) { var it = self.makeIterator() @@ -115,7 +115,7 @@ extension BigSubstring: Hashable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: Comparable { public static func < (left: Self, right: Self) -> Bool { // FIXME: Implement properly normalized comparisons & hashing. @@ -140,7 +140,7 @@ extension BigSubstring: Comparable { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: Sequence { public typealias Element = Character @@ -164,7 +164,7 @@ extension BigSubstring: Sequence { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: BidirectionalCollection { public typealias Index = BigString.Index public typealias SubSequence = Self @@ -225,7 +225,7 @@ extension BigSubstring: BidirectionalCollection { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring { public func index(roundingDown i: Index) -> Index { precondition(i >= startIndex && i <= endIndex, "Index out of bounds") @@ -238,7 +238,7 @@ extension BigSubstring { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring { /// Run the closure `body` to mutate the contents of this view within `range`, then update /// the bounds of this view to maintain an approximation of their logical position in the @@ -273,7 +273,7 @@ extension BigSubstring { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension BigSubstring: RangeReplaceableCollection { public init() { let str = BigString() diff --git a/Sources/RopeModule/Rope/Basics/Rope+_Node.swift b/Sources/RopeModule/Rope/Basics/Rope+_Node.swift index d60b9c02a..9c10f12af 100644 --- a/Sources/RopeModule/Rope/Basics/Rope+_Node.swift +++ b/Sources/RopeModule/Rope/Basics/Rope+_Node.swift @@ -134,15 +134,19 @@ extension Rope._Node { } extension Rope._Node { - @inlinable @inline(__always) + @inlinable + @_transparent internal mutating func isUnique() -> Bool { isKnownUniquelyReferenced(&object) } @inlinable internal mutating func ensureUnique() { - guard !isKnownUniquelyReferenced(&object) else { return } + guard !isKnownUniquelyReferenced(&object) else { + return + } self = copy() + assert(isUnique()) } @inlinable @inline(never) diff --git a/Sources/RopeModule/Utilities/String Utilities.swift b/Sources/RopeModule/Utilities/String Utilities.swift index 8e6ddd7f7..18f93a44c 100644 --- a/Sources/RopeModule/Utilities/String Utilities.swift +++ b/Sources/RopeModule/Utilities/String Utilities.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension StringProtocol { @inline(__always) var _indexOfLastCharacter: Index { @@ -45,7 +45,7 @@ extension StringProtocol { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension String { internal func _lpad(to width: Int, with pad: Character = " ") -> String { let c = self.count @@ -60,7 +60,7 @@ extension String { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension String { @discardableResult mutating func _appendQuotedProtectingLeft( diff --git a/Sources/RopeModule/Utilities/_CharacterRecognizer.swift b/Sources/RopeModule/Utilities/_CharacterRecognizer.swift index 5be57bcb6..6e9a4314b 100644 --- a/Sources/RopeModule/Utilities/_CharacterRecognizer.swift +++ b/Sources/RopeModule/Utilities/_CharacterRecognizer.swift @@ -9,10 +9,10 @@ // //===----------------------------------------------------------------------===// -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) internal typealias _CharacterRecognizer = Unicode._CharacterRecognizer -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension _CharacterRecognizer { internal func _isKnownEqual(to other: Self) -> Bool { if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { // SwiftStdlib 5.9 @@ -23,7 +23,7 @@ extension _CharacterRecognizer { } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension _CharacterRecognizer { mutating func firstBreak( in str: Substring @@ -52,7 +52,7 @@ extension _CharacterRecognizer { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) extension _CharacterRecognizer { init(partialCharacter: Substring.UnicodeScalarView) { self.init() diff --git a/Tests/ArrayTests/ArrayLayout.swift b/Tests/ArrayTests/ArrayLayout.swift new file mode 100644 index 000000000..56ab2c314 --- /dev/null +++ b/Tests/ArrayTests/ArrayLayout.swift @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import _CollectionsTestSupport +import ArrayModule +#endif + +struct ArrayLayout { + var capacity: Int + var count: Int + + init(capacity: Int, count: Int) { + precondition(count >= 0 && count <= capacity) + self.capacity = capacity + self.count = count + } +} + +func withSomeArrayLayouts( + _ label: String, + ofCapacities capacities: some Sequence, + file: StaticString = #filePath, + line: UInt = #line, + run body: (ArrayLayout) throws(E) -> Void +) throws(E) { + let context = TestContext.current + for capacity in capacities { + var counts: Set = [] + counts.insert(0) + counts.insert(capacity) + counts.insert(capacity / 2) + if capacity >= 1 { + counts.insert(1) + counts.insert(capacity - 1) + } + if capacity >= 2 { + counts.insert(2) + counts.insert(capacity - 2) + } + for count in counts { + let layout = ArrayLayout(capacity: capacity, count: count) + let entry = context.push("\(label): \(layout)", file: file, line: line) + + var done = false + defer { + context.pop(entry) + if !done { + print(context.currentTrace(title: "Throwing trace")) + } + } + try body(layout) + done = true + } + } +} + +#if compiler(>=6.2) && (compiler(>=6.3) || !os(Windows)) // FIXME: [2025-08-17] Windows has no 6.2 snapshot with OutputSpan +extension RigidArray where Element: ~Copyable { + init(layout: ArrayLayout, using generator: (Int) -> Element) { + self.init(capacity: layout.capacity) { span in + for i in 0 ..< layout.count { + span.append(generator(i)) + } + } + } +} + +extension DynamicArray where Element: ~Copyable { + init(layout: ArrayLayout, using generator: (Int) -> Element) { + self.init(consuming: RigidArray(layout: layout, using: generator)) + } +} + +extension LifetimeTracker { + func rigidArray( + layout: ArrayLayout, + using generator: (Int) -> Element = { $0 } + ) -> RigidArray> { + RigidArray(layout: layout, using: { self.instance(for: generator($0)) }) + } + + func dynamicArray( + layout: ArrayLayout, + using generator: (Int) -> Element = { $0 } + ) -> DynamicArray> { + let contents = RigidArray(layout: layout) { + self.instance(for: generator($0)) + } + return DynamicArray(consuming: contents) + } +} +#endif diff --git a/Tests/ArrayTests/DynamicArrayTests.swift b/Tests/ArrayTests/DynamicArrayTests.swift new file mode 100644 index 000000000..50fd53921 --- /dev/null +++ b/Tests/ArrayTests/DynamicArrayTests.swift @@ -0,0 +1,879 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import _CollectionsTestSupport +import ArrayModule +#endif + +#if compiler(>=6.2) && (compiler(>=6.3) || !os(Windows)) // FIXME: [2025-08-17] Windows has no 6.2 snapshot with OutputSpan + +#if !COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 5.0, *) +public func expectContainerContents< + Element: Equatable, + C2: Collection, +>( + _ left: borrowing DynamicArray, + equalTo right: C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + expectContainerContents( + left.span, + equalTo: right, + message(), trapping: trapping, file: file, line: line) +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 5.0, *) +public func expectContainerContents< + E1: ~Copyable, + C2: Collection, +>( + _ left: borrowing DynamicArray, + equivalentTo right: C2, + by areEquivalent: (borrowing E1, C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + expectContainerContents( + left.span, + equivalentTo: right, + by: areEquivalent, + message(), trapping: trapping, file: file, line: line) +} +#endif + + +@available(SwiftStdlib 6.2, *) +class DynamicArrayTests: CollectionTestCase { + func test_validate_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let items = DynamicArray(consuming: tracker.rigidArray(layout: layout)) + let expected = (0 ..< layout.count).map { tracker.instance(for: $0) } + expectEqual(tracker.instances, 2 * layout.count) + expectContainerContents(items, equalTo: expected) +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + checkContainer(items, expectedContents: expected) +#endif + } + } + } + + func test_basics() { + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct + + var array = DynamicArray() + expectTrue(array.isEmpty) + expectEqual(array.count, 0) + expectEqual(array.capacity, 0) + expectEqual(tracker.instances, 0) + + array.append(tracker.structInstance(for: 10)) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array.capacity, 1) // This assumes a specific growth behavior + expectEqual(array[0].payload, 10) + expectEqual(tracker.instances, 1) + + array.append(tracker.structInstance(for: 20)) + expectFalse(array.isEmpty) + expectEqual(array.count, 2) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectEqual(array[0].payload, 10) + expectEqual(array[1].payload, 20) + expectEqual(tracker.instances, 2) + + let old = array.remove(at: 0) + expectEqual(old.payload, 10) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectEqual(array[0].payload, 20) + expectEqual(tracker.instances, 2) + _ = consume old + expectEqual(tracker.instances, 1) + + let old2 = array.remove(at: 0) + expectEqual(old2.payload, 20) + expectEqual(array.count, 0) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectTrue(array.isEmpty) + expectEqual(tracker.instances, 1) + _ = consume old2 + expectEqual(tracker.instances, 0) + } + } + + func test_init_capacity() { + do { + let a = DynamicArray(capacity: 0) + expectEqual(a.capacity, 0) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 0) + expectTrue(a.isEmpty) + } + + do { + let a = DynamicArray(capacity: 10) + expectEqual(a.capacity, 10) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 10) + expectTrue(a.isEmpty) + } + } + + func test_init_generator() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + + func test_init_repeating() { + withEvery("c", in: [0, 10, 100]) { c in + withLifetimeTracking { tracker in + let value = tracker.instance(for: 0) + let a = DynamicArray(repeating: value, count: c) + expectEqual(a.capacity, c) + expectEqual(a.count, c) + expectEqual(a.freeCapacity, 0) + expectEqual(a.isEmpty, c == 0) + for i in 0 ..< c { + expectIdentical(a[i], value) + } + } + } + } + + func test_init_copying_Sequence() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = DynamicArray( + capacity: layout.capacity, + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }) + expectEqual(tracker.instances, layout.count) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.isEmpty, layout.count == 0) + for i in 0 ..< layout.count { + expectEqual(a[i].payload, i) + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_init_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("spanCounts", in: [ + [1], + [3, 5, 7], + [10, 3], + ] as [[Int]]) { spanCounts in + withLifetimeTracking { tracker in + let additions = StaccatoContainer( + contents: RigidArray( + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }), + spanCounts: spanCounts) + + let array = DynamicArray( + capacity: layout.capacity, copying: additions) + expectEqual(tracker.instances, layout.count) + expectEqual(array.capacity, layout.capacity) + expectEqual(array.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(array[i].payload, i) + } + } + } + } + } +#endif + + func test_span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let span = a.span + expectEqual(span.count, layout.count) + for i in 0 ..< span.count { + expectEqual(span[i].payload, i) + } + } + } + } + + func test_mutableSpan() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + var span = a.mutableSpan + expectEqual(span.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(span[i].payload, i) + span[i] = tracker.instance(for: -i) + } + for i in 0 ..< layout.count { + expectEqual(span[i].payload, -i) + } + for i in 0 ..< layout.count { + expectEqual(a[i].payload, -i) + } + } + } + } + + func test_nextSpan() { + // DynamicArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let whole = a.span + var i = 0 + let first = a.span(after: &i) + expectEqual(i, layout.count) + expectTrue(first.isIdentical(to: whole)) + let second = a.span(after: &i) + expectEqual(i, layout.count) + expectTrue(second.isEmpty) + } + } + } + + func test_previousSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let whole = a.span + var i = layout.count + let first = a.span(before: &i) + expectEqual(i, 0) + expectTrue(first.isIdentical(to: whole)) + let second = a.span(before: &i) + expectEqual(i, 0) + expectTrue(second.isEmpty) + } + } + } + + func test_nextMutableSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + var i = 0 + var span = a.mutableSpan(after: &i) + expectEqual(i, layout.count) + expectEqual(span.count, layout.count) + span = a.mutableSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(span.isEmpty) + } + } + } + + func test_index_properties() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + let a = DynamicArray(layout: layout, using: { $0 }) + expectEqual(a.startIndex, 0) + expectEqual(a.endIndex, layout.count) + expectEqual(a.indices, 0 ..< layout.count) + } + } + + func test_swapAt() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + withEvery("i", in: 0 ..< layout.count / 2) { i in + a.swapAt(i, layout.count - 1 - i) + } + let expected = (0 ..< layout.count).reversed() + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_borrowElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + for i in 0 ..< layout.count { + let item = a.borrowElement(at: i) + expectEqual(item[].payload, i) + } + } + } + } +#endif + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_mutateElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + for i in 0 ..< layout.count { + var item = a.mutateElement(at: i) + expectEqual(item[].payload, i) + item[] = tracker.instance(for: -i) + expectEqual(tracker.instances, layout.count) + } + } + } + } +#endif + + func test_edit() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + a.edit { span in + expectEqual(span.capacity, layout.capacity) + expectEqual(span.count, layout.count) + if layout.capacity > 0 { + // FIXME: OutputSpan.removeAll crashes when empty in some 6.2 snapshots (rdar://158440246) + span.removeAll() + } + expectEqual(tracker.instances, 0) + } + expectEqual(a.count, 0) + + a.edit { span in + expectEqual(span.capacity, layout.capacity) + expectEqual(span.count, 0) + for i in 0 ..< span.capacity { + span.append(tracker.instance(for: -i)) + } + expectEqual(tracker.instances, layout.capacity) + } + expectEqual(a.count, layout.capacity) + + struct TestError: Error {} + + expectThrows { + try a.edit { span in + expectEqual(tracker.instances, layout.capacity) + while !span.isEmpty { + if span.count == layout.count { break } + let old = span.removeLast() + expectEqual(old.payload, -span.count) + } + throw TestError() + } + } + errorHandler: { error in + expectTrue(error is TestError) + } + expectContainerContents( + a, + equivalentTo: (0 ..< layout.count).map { -$0 }, + by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_reallocate() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + layout.capacity, layout.count, layout.count + 1, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reallocate(capacity: newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_reserveCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + 0, layout.count - 1, layout.count, layout.count + 1, + layout.capacity, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reserveCapacity(newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + a.removeAll() + expectTrue(a.isEmpty) + expectEqual(a.capacity, 0) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeAll_keepingCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + a.removeAll(keepingCapacity: true) + expectTrue(a.isEmpty) + expectEqual(a.capacity, layout.capacity) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + withEvery("i", in: 0 ..< layout.count) { i in + let old = a.removeLast() + expectEqual(old.payload, layout.count - 1 - i) + expectEqual(a.count, layout.count - 1 - i) + expectEqual(a.capacity, layout.capacity) + } + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast_k() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("k", in: 0 ..< layout.count) { k in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeLast(k) + + var a = tracker.dynamicArray(layout: layout) + expectEqual(tracker.instances, layout.count) + a.removeLast(k) + expectEqual(tracker.instances, layout.count - k) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_remove_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ..< layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.remove(at: i) + + var a = tracker.dynamicArray(layout: layout) + let old = a.remove(at: i) + expectEqual(old.payload, i) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeSubrange() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeSubrange(range) + + var a = tracker.dynamicArray(layout: layout) + a.removeSubrange(range) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_removeAll_where() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeAll(where: { $0.isMultiple(of: 2) }) + + var a = tracker.dynamicArray(layout: layout) + a.removeAll(where: { $0.payload.isMultiple(of: 2) }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } +#endif + + func test_popLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let expectedItem = expected.popLast() + + var a = tracker.dynamicArray(layout: layout) + let item = a.popLast() + + expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_append_geometric_growth() { + // This test depends on the precise growth curve of DynamicArray, + // which is not part of its stable API. The test may need to be updated + // accordingly. + withLifetimeTracking { tracker in + typealias Value = LifetimeTracked + + var array = DynamicArray() + expectEqual(array.capacity, 0) + + array.append(tracker.instance(for: 0)) + expectEqual(array.capacity, 1) + array.append(tracker.instance(for: 1)) + expectEqual(array.capacity, 2) + array.append(tracker.instance(for: 2)) + expectEqual(array.capacity, 3) + array.append(tracker.instance(for: 3)) + expectEqual(array.capacity, 5) + array.append(tracker.instance(for: 4)) + expectEqual(array.capacity, 5) + array.append(tracker.instance(for: 5)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 6)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 7)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 8)) + expectEqual(array.capacity, 12) + + for i in 9 ..< 100 { + array.append(tracker.instance(for: i)) + } + expectEqual(tracker.instances, 100) + expectEqual(array.count, 100) + expectEqual(array.capacity, 140) + + do { + let additions = RigidArray(capacity: 300) { span in + for i in 0 ..< 300 { + span.append(tracker.instance(for: 100 + i)) + } + } + array.append(copying: additions.span) + } + expectEqual(array.capacity, 400) + + expectEqual(tracker.instances, 400) + for i in 0 ..< 400 { + expectEqual(array[i].payload, i) + } + + _ = consume array + expectEqual(tracker.instances, 0) + } + } + + func test_append() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let c = 2 * layout.capacity + 10 + var a = tracker.dynamicArray(layout: layout) + for i in layout.count ..< c { + a.append(tracker.instance(for: i)) + expectEqual(a.count, i + 1) + expectContainerContents(a, equivalentTo: 0 ..< i + 1, by: { $0.payload == $1 }) + } + expectEqual(tracker.instances, c) + } + } + } + + func test_append_copying_MinimalSequence() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + let c = 2 * layout.capacity + 10 + var a = tracker.dynamicArray(layout: layout) + a.append(copying: MinimalSequence( + elements: (layout.count ..< c).map { tracker.instance(for: $0) }, + underestimatedCount: .half, + isContiguous: isContiguous)) + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1}) + expectEqual(tracker.instances, c) + } + } + } + } + + func test_append_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("additions", in: [0, 1, 10, 100]) { additions in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + let b = RigidArray(capacity: additions) { span in + for i in 0 ..< additions { + span.append(tracker.instance(for: layout.count + i)) + } + } + a.append(copying: b.span) + let c = layout.count + additions + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1 }) + expectEqual(tracker.instances, c) + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_append_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("additions", in: [0, 1, 10, 100]) { additions in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + + let c = layout.count + additions + let addition = (layout.count ..< c).map { + tracker.instance(for: $0) + } + let b = StaccatoContainer( + contents: RigidArray(copying: addition), + spanCounts: [spanCount]) + a.append(copying: b) + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1 }) + expectEqual(tracker.instances, c) + } + } + } + } + } +#endif + + func test_insert_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.insert(-1, at: i) + + var a = tracker.dynamicArray(layout: layout) + a.insert(tracker.instance(for: -1), at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + 1) + } + } + } + } + + func test_insert_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + let addition = (layout.count ..< layout.count + c) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + let trackedAddition = addition.map { tracker.instance(for: $0) } + var a = tracker.dynamicArray(layout: layout) + a.insert(copying: trackedAddition, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + + func test_insert_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + let addition = Array(layout.count ..< layout.count + c) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + let rigidAddition = RigidArray( + copying: (0 ..< addition.count).lazy.map { tracker.instance(for: addition[$0]) } + ) + var a = tracker.dynamicArray(layout: layout) + a.insert(copying: rigidAddition.span, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_insert_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + + var expected = Array(0 ..< layout.count) + let addition = Array(layout.count ..< layout.count + c) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.dynamicArray(layout: layout) + let rigidAddition = StaccatoContainer( + contents: RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + }, + spanCounts: [spanCount]) + a.insert(copying: rigidAddition, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + } +#endif + + func test_replaceSubrange_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = addition.map { tracker.instance(for: $0) } + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + + func test_replaceSubrange_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = RigidArray( + copying: addition.map { tracker.instance(for: $0) }) + a.replaceSubrange(range, copying: trackedAddition.span) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_replaceSubrange_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = StaccatoContainer( + contents: RigidArray( + copying: addition.map { tracker.instance(for: $0) }), + spanCounts: [spanCount]) + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + } +#endif +} +#endif diff --git a/Tests/ArrayTests/RigidArrayTests.swift b/Tests/ArrayTests/RigidArrayTests.swift new file mode 100644 index 000000000..ae6a3abe4 --- /dev/null +++ b/Tests/ArrayTests/RigidArrayTests.swift @@ -0,0 +1,825 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import _CollectionsTestSupport +import ArrayModule +#endif + +#if compiler(>=6.2) && (compiler(>=6.3) || !os(Windows)) // FIXME: [2025-08-17] Windows has no 6.2 snapshot with OutputSpan + +#if !COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 5.0, *) +public func expectContainerContents< + Element: Equatable, + C2: Collection, +>( + _ left: borrowing RigidArray, + equalTo right: C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + expectContainerContents( + left.span, + equalTo: right, + message(), trapping: trapping, file: file, line: line) +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 5.0, *) +public func expectContainerContents< + E1: ~Copyable, + C2: Collection, +>( + _ left: borrowing RigidArray, + equivalentTo right: C2, + by areEquivalent: (borrowing E1, C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + expectContainerContents( + left.span, + equivalentTo: right, + by: areEquivalent, + message(), trapping: trapping, file: file, line: line) +} +#endif + +@available(SwiftStdlib 5.0, *) +class RigidArrayTests: CollectionTestCase { + func test_validate_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let items = tracker.rigidArray(layout: layout) + let expected = (0 ..< layout.count).map { tracker.instance(for: $0) } + expectEqual(tracker.instances, 2 * layout.count) + expectContainerContents(items, equalTo: expected) +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + checkContainer(items, expectedContents: expected) +#endif + } + } + } + + func test_empty() { + let a = RigidArray() + expectEqual(a.capacity, 0) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 0) + expectTrue(a.isEmpty) + expectTrue(a.isFull) + } + + func test_init_capacity() { + do { + let a = RigidArray(capacity: 0) + expectEqual(a.capacity, 0) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 0) + expectTrue(a.isEmpty) + expectTrue(a.isFull) + } + + do { + let a = RigidArray(capacity: 10) + expectEqual(a.capacity, 10) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 10) + expectTrue(a.isEmpty) + expectFalse(a.isFull) + } + } + + func test_init_generator() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectEqual(a.isFull, layout.count == layout.capacity) + expectContainerContents( + a, + equivalentTo: 0 ..< layout.count, + by: { $0.payload == $1 }) + } + } + } + + func test_init_repeating() { + withEvery("c", in: [0, 10, 100]) { c in + withLifetimeTracking { tracker in + let value = tracker.instance(for: 0) + let a = RigidArray(repeating: value, count: c) + expectEqual(a.capacity, c) + expectEqual(a.count, c) + expectEqual(a.freeCapacity, 0) + expectEqual(a.isEmpty, c == 0) + expectTrue(a.isFull) + for i in 0 ..< c { + expectIdentical(a[i], value) + } + } + } + } + + func test_init_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = RigidArray( + capacity: layout.capacity, + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }) + expectEqual(tracker.instances, layout.count) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectEqual(a.isFull, layout.count == layout.capacity) + for i in 0 ..< layout.count { + expectEqual(a[i].payload, i) + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_init_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("spanCounts", in: [ + [1], + [3, 5, 7], + [10, 3], + ] as [[Int]]) { spanCounts in + withLifetimeTracking { tracker in + let additions = StaccatoContainer( + contents: RigidArray( + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }), + spanCounts: spanCounts) + + let array = RigidArray(capacity: layout.capacity, copying: additions) + expectEqual(tracker.instances, layout.count) + expectEqual(array.capacity, layout.capacity) + expectEqual(array.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(array[i].payload, i) + } + } + } + } + } +#endif + + func test_span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let span = a.span + expectEqual(span.count, layout.count) + for i in 0 ..< span.count { + expectEqual(span[i].payload, i) + } + } + } + } + + func test_mutableSpan() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + var span = a.mutableSpan + expectEqual(span.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(span[i].payload, i) + span[i] = tracker.instance(for: -i) + } + for i in 0 ..< layout.count { + expectEqual(span[i].payload, -i) + } + for i in 0 ..< layout.count { + expectEqual(a[i].payload, -i) + } + } + } + } + + func test_nextSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let whole = a.span + var i = 0 + let first = a.span(after: &i) + expectEqual(i, layout.count) + expectTrue(first.isIdentical(to: whole)) + let second = a.span(after: &i) + expectEqual(i, layout.count) + expectTrue(second.isEmpty) + } + } + } + + func test_previousSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let whole = a.span + var i = layout.count + let first = a.span(before: &i) + expectEqual(i, 0) + expectTrue(first.isIdentical(to: whole)) + let second = a.span(before: &i) + expectEqual(i, 0) + expectTrue(second.isEmpty) + } + } + } + + func test_nextMutableSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + var i = 0 + var span = a.mutableSpan(after: &i) + expectEqual(i, layout.count) + expectEqual(span.count, layout.count) + span = a.mutableSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(span.isEmpty) + } + } + } + + func test_index_properties() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + let a = RigidArray(layout: layout, using: { $0 }) + expectEqual(a.startIndex, 0) + expectEqual(a.endIndex, layout.count) + expectEqual(a.indices, 0 ..< layout.count) + } + } + + func test_swapAt() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + withEvery("i", in: 0 ..< layout.count / 2) { i in + a.swapAt(i, layout.count - 1 - i) + } + let expected = (0 ..< layout.count).reversed() + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_subscript_borrow() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + for i in 0 ..< layout.count { + expectEqual(a[i].payload, i) + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_borrowElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + for i in 0 ..< layout.count { + let item = a.borrowElement(at: i) + expectEqual(item[].payload, i) + } + } + } + } +#endif + + func test_mutateElement() { + func modify(tracker: LifetimeTracker, i: Int, item: inout LifetimeTracked) { + expectEqual(item.payload, i) + item = tracker.instance(for: -i) + } + + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + for i in 0 ..< layout.count { + modify(tracker: tracker, i: i, item: &a[i]) + expectEqual(tracker.instances, layout.count) + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_mutateElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + for i in 0 ..< layout.count { + var item = a.mutateElement(at: i) + expectEqual(item[].payload, i) + item[] = tracker.instance(for: -i) + expectEqual(tracker.instances, layout.count) + } + } + } + } +#endif + + func test_edit() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.edit { span in + expectEqual(span.capacity, layout.capacity) + expectEqual(span.count, layout.count) + if span.count > 0 { // FIXME: rdar://158440246 + span.removeAll() + } + expectEqual(tracker.instances, 0) + } + expectEqual(a.count, 0) + + a.edit { span in + expectEqual(span.capacity, layout.capacity) + expectEqual(span.count, 0) + for i in 0 ..< span.capacity { + span.append(tracker.instance(for: -i)) + } + expectEqual(tracker.instances, layout.capacity) + } + expectEqual(a.count, layout.capacity) + + struct TestError: Error {} + + expectThrows { + try a.edit { span in + expectEqual(tracker.instances, layout.capacity) + while !span.isEmpty { + if span.count == layout.count { break } + let old = span.removeLast() + expectEqual(old.payload, -span.count) + } + throw TestError() + } + } + errorHandler: { error in + expectTrue(error is TestError) + } + expectContainerContents( + a, + equivalentTo: (0 ..< layout.count).map { -$0 }, + by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_reallocate() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + layout.capacity, layout.count, layout.count + 1, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reallocate(capacity: newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_reserveCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + 0, layout.count - 1, layout.count, layout.count + 1, + layout.capacity, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reserveCapacity(newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_copy() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let b = a.copy() + expectEqual(b.count, layout.count) + expectEqual(b.capacity, layout.capacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectContainerContents(b, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + + func test_copy_capacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [layout.count, layout.count + 1, layout.capacity, layout.capacity + 1] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let b = a.copy(capacity: newCapacity) + expectEqual(b.count, layout.count) + expectEqual(b.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectContainerContents(b, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.removeAll() + expectTrue(a.isEmpty) + expectEqual(a.capacity, layout.capacity) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + withEvery("i", in: 0 ..< layout.count) { i in + let old = a.removeLast() + expectEqual(old.payload, layout.count - 1 - i) + expectEqual(a.count, layout.count - 1 - i) + expectEqual(a.capacity, layout.capacity) + } + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast_k() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("k", in: 0 ..< layout.count) { k in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeLast(k) + + var a = tracker.rigidArray(layout: layout) + expectEqual(tracker.instances, layout.count) + a.removeLast(k) + expectEqual(tracker.instances, layout.count - k) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_remove_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ..< layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.remove(at: i) + + var a = tracker.rigidArray(layout: layout) + let old = a.remove(at: i) + expectEqual(old.payload, i) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeSubrange() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeSubrange(range) + + var a = tracker.rigidArray(layout: layout) + a.removeSubrange(range) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_removeAll_where() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeAll(where: { $0.isMultiple(of: 2) }) + + var a = tracker.rigidArray(layout: layout) + a.removeAll(where: { $0.payload.isMultiple(of: 2) }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } +#endif + + func test_popLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let expectedItem = expected.popLast() + + var a = tracker.rigidArray(layout: layout) + let item = a.popLast() + + expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_append() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + for i in layout.count ..< layout.capacity { + a.append(tracker.instance(for: i)) + expectEqual(a.count, i + 1) + expectContainerContents(a, equivalentTo: 0 ..< i + 1, by: { $0.payload == $1 }) + } + expectTrue(a.isFull) + expectEqual(tracker.instances, layout.capacity) + } + } + } + + func test_append_copying_MinimalSequence() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.append(copying: MinimalSequence( + elements: (layout.count ..< layout.capacity).map { tracker.instance(for: $0) }, + underestimatedCount: .half, + isContiguous: isContiguous)) + expectTrue(a.isFull) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + + func test_append_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + let b = tracker.rigidArray( + layout: ArrayLayout( + capacity: layout.capacity - layout.count, + count: layout.capacity - layout.count), + using: { layout.count + $0 }) + a.append(copying: b.span) + expectTrue(a.isFull) + expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_append_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + + let addition = (layout.count ..< layout.capacity).map { tracker.instance(for: $0) } + let b = StaccatoContainer( + contents: RigidArray(copying: addition), + spanCounts: [spanCount]) + a.append(copying: b) + expectTrue(a.isFull) + expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } +#endif + + func test_insert_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + guard layout.count < layout.capacity else { return } + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.insert(-1, at: i) + + var a = tracker.rigidArray(layout: layout) + a.insert(tracker.instance(for: -1), at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + 1) + } + } + } + } + + func test_insert_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + let addition = (layout.count ..< layout.capacity) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + let trackedAddition = MinimalCollection( + addition.map { tracker.instance(for: $0) }, + isContiguous: isContiguous) + var a = tracker.rigidArray(layout: layout) + a.insert(copying: trackedAddition, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + } + + func test_insert_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + let addition = Array(layout.count ..< layout.capacity) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.rigidArray(layout: layout) + let rigidAddition = RigidArray(capacity: addition.count) { span in + for i in 0 ..< addition.count { + span.append(tracker.instance(for: addition[i])) + } + } + a.insert(copying: rigidAddition.span, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_insert_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + + var expected = Array(0 ..< layout.count) + let addition = Array(layout.count ..< layout.capacity) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.rigidArray(layout: layout) + let rigidAddition = StaccatoContainer( + contents: RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + }, + spanCounts: [Swift.max(spanCount, 1)]) + a.insert(copying: rigidAddition, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + } +#endif + + func test_replaceSubrange_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + let trackedAddition = MinimalCollection( + addition.map { tracker.instance(for: $0) }, + isContiguous: isContiguous) + var a = tracker.rigidArray(layout: layout) + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + } + func test_replaceSubrange_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.rigidArray(layout: layout) + let trackedAddition = RigidArray(copying: addition.map { tracker.instance(for: $0) }) + a.replaceSubrange(range, copying: trackedAddition.span) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + func test_replaceSubrange_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.rigidArray(layout: layout) + let trackedAddition = StaccatoContainer( + contents: RigidArray(copying: addition.map { tracker.instance(for: $0) }), + spanCounts: [spanCount]) + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + } +#endif +} +#endif diff --git a/Tests/BitCollectionsTests/BitSet.Counted Tests.swift b/Tests/BitCollectionsTests/BitSet.Counted Tests.swift index e28676816..c9f290e33 100644 --- a/Tests/BitCollectionsTests/BitSet.Counted Tests.swift +++ b/Tests/BitCollectionsTests/BitSet.Counted Tests.swift @@ -75,7 +75,7 @@ final class BitSetCountedTests: CollectionTestCase { func check( _ expected: S, _ body: (inout BitSet.Counted) -> Void, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element == Int { var a: BitSet.Counted = [1, 2, 3, 4] @@ -96,7 +96,7 @@ final class BitSetCountedTests: CollectionTestCase { func check( _ expected: S, _ body: (inout BitSet.Counted) -> Void, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element == Int { var a: BitSet.Counted = [1, 2, 3, 4] @@ -117,7 +117,7 @@ final class BitSetCountedTests: CollectionTestCase { func check( _ expected: S, _ body: (inout BitSet.Counted) -> Void, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element == Int { var a: BitSet.Counted = [1, 2, 3, 4] @@ -138,7 +138,7 @@ final class BitSetCountedTests: CollectionTestCase { func check( _ expected: S, _ body: (inout BitSet.Counted) -> Void, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element == Int { var a: BitSet.Counted = [1, 2, 3, 4] diff --git a/Tests/BitCollectionsTests/BitSetTests.swift b/Tests/BitCollectionsTests/BitSetTests.swift index ace4d570c..7f57e7b30 100644 --- a/Tests/BitCollectionsTests/BitSetTests.swift +++ b/Tests/BitCollectionsTests/BitSetTests.swift @@ -129,7 +129,7 @@ final class BitSetTest: CollectionTestCase { func withInterestingSets( _ label: String, maximum: Int, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, run body: (Set) -> Void ) { diff --git a/Tests/ContainersTests/BorrowTests.swift b/Tests/ContainersTests/BorrowTests.swift new file mode 100644 index 000000000..f6c0ed6df --- /dev/null +++ b/Tests/ContainersTests/BorrowTests.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if COLLECTIONS_CONTAINERS_PREVIEW +import XCTest +import ContainersPreview +import Synchronization + +final class BorrowTests: XCTestCase { + @available(SwiftStdlib 6.2, *) + func test_basic() { + let x: Atomic? = Atomic(0) + + if let y = x.borrow() { + XCTAssertEqual(y[].load(ordering: .relaxed), 0) + } + } +} +#endif diff --git a/Tests/ContainersTests/BoxTests.swift b/Tests/ContainersTests/BoxTests.swift new file mode 100644 index 000000000..163dc5d90 --- /dev/null +++ b/Tests/ContainersTests/BoxTests.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import _CollectionsTestSupport +import ContainersPreview +#endif + +#if compiler(>=6.2) +final class BoxTests: XCTestCase { + func test_basic() { + var intOnHeap = Box(0) + + XCTAssertEqual(intOnHeap[], 0) + + intOnHeap[] = 123 + + XCTAssertEqual(intOnHeap[], 123) + + XCTAssertEqual(intOnHeap.copy(), 123) + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + var inoutToIntOnHeap = intOnHeap.leak() + + XCTAssertEqual(inoutToIntOnHeap[], 123) + + inoutToIntOnHeap[] = 321 + + XCTAssertEqual(inoutToIntOnHeap[], 321) +#endif + } +} +#endif diff --git a/Tests/ContainersTests/InoutTests.swift b/Tests/ContainersTests/InoutTests.swift new file mode 100644 index 000000000..6bd2f0b7f --- /dev/null +++ b/Tests/ContainersTests/InoutTests.swift @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import _CollectionsTestSupport +import ContainersPreview +#endif + +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +final class InoutTests: XCTestCase { + func test_basic() { + var x = 0 + var y = Inout(&x) + + var v = y[] + XCTAssertEqual(v, 0) + + y[] += 10 + + v = y[] + XCTAssertEqual(v, 10) + XCTAssertEqual(x, 10) + } +} +#endif diff --git a/Tests/DequeTests/DequeInternals.swift b/Tests/DequeTests/DequeInternals.swift index c34a3f18f..f9fc9b4bb 100644 --- a/Tests/DequeTests/DequeInternals.swift +++ b/Tests/DequeTests/DequeInternals.swift @@ -66,7 +66,7 @@ func withEveryDeque( _ label: String, ofCapacities capacities: C, startValue: Int = 0, - file: StaticString = #file, line: UInt = #line, + file: StaticString = #filePath, line: UInt = #line, _ body: (DequeLayout) throws -> Void ) rethrows -> Void where C.Element == Int { // Exhaustive tests for all deque layouts of various capacities diff --git a/Tests/DequeTests/MutableCollectionTests.swift b/Tests/DequeTests/MutableCollectionTests.swift index 364a1b8dd..4722c4380 100644 --- a/Tests/DequeTests/MutableCollectionTests.swift +++ b/Tests/DequeTests/MutableCollectionTests.swift @@ -47,7 +47,7 @@ final class MutableCollectiontests: CollectionTestCase { _ item: inout LifetimeTracked, tracker: LifetimeTracker, delta: Int, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { expectTrue(isKnownUniquelyReferenced(&item)) diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift index 247deb70a..5cf5887ac 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift @@ -274,7 +274,7 @@ class TreeDictionaryTests: CollectionTestCase { d, expectedContents: ref, by: ==) } - @available(macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4, *) + @available(SwiftStdlib 5.6, *) struct FancyDictionaryKey: CodingKeyRepresentable, Hashable, Codable { var value: Int @@ -314,7 +314,7 @@ class TreeDictionaryTests: CollectionTestCase { ]) expectEqual(try MinimalEncoder.encode(d2), v2) - if #available(macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4, *) { + if #available(SwiftStdlib 5.6, *) { let d3: TreeDictionary = [ FancyDictionaryKey(1): 10, FancyDictionaryKey(2): 20 ] diff --git a/Tests/HashTreeCollectionsTests/TreeSet Tests.swift b/Tests/HashTreeCollectionsTests/TreeSet Tests.swift index ce018b6e1..1014b99a3 100644 --- a/Tests/HashTreeCollectionsTests/TreeSet Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeSet Tests.swift @@ -601,7 +601,7 @@ class TreeSetTests: CollectionTestCase { func check( _ reference: Set, _ body: (inout TreeSet) -> Void, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var set = x diff --git a/Tests/HashTreeCollectionsTests/Utilities.swift b/Tests/HashTreeCollectionsTests/Utilities.swift index 6e4569179..eb32332ee 100644 --- a/Tests/HashTreeCollectionsTests/Utilities.swift +++ b/Tests/HashTreeCollectionsTests/Utilities.swift @@ -123,7 +123,7 @@ func expectEqualSets( _ ref: [Element], _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { expectEqualSets( @@ -138,7 +138,7 @@ func expectEqualSets( _ ref: Set, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var ref = ref @@ -176,7 +176,7 @@ func expectEqualDictionaries( _ ref: [(key: Key, value: Value)], _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { expectEqualDictionaries( @@ -191,7 +191,7 @@ func expectEqualDictionaries( _ dict: Dictionary, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { expectEqual(map.count, dict.count, "Mismatching count", file: file, line: line) diff --git a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Values Tests.swift b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Values Tests.swift index e6c04040d..b731545c5 100644 --- a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Values Tests.swift +++ b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary+Values Tests.swift @@ -105,7 +105,7 @@ class OrderedDictionaryValueTests: CollectionTestCase { _ item: inout LifetimeTracked, tracker: LifetimeTracker, delta: Int, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { expectTrue(isKnownUniquelyReferenced(&item)) diff --git a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift index b5ea03f20..e30c3f129 100644 --- a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift +++ b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2021 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2021 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -18,13 +18,13 @@ import _CollectionsTestSupport #endif class MeasuringHashable: Hashable { - static var equalityChecks = 0 + nonisolated(unsafe) static var equalityChecks = 0 static func == (lhs: MeasuringHashable, rhs: MeasuringHashable) -> Bool { MeasuringHashable.equalityChecks += 1 return lhs._inner == rhs._inner } - static var hashChecks = 0 + nonisolated(unsafe) static var hashChecks = 0 func hash(into hasher: inout Hasher) { MeasuringHashable.hashChecks += 1 _inner.hash(into: &hasher) @@ -37,7 +37,7 @@ class MeasuringHashable: Hashable { } } -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +@available(SwiftStdlib 5.1, *) class OrderedSetDiffingTests: CollectionTestCase { func _validatePerformance(from a: OrderedSet, to b: OrderedSet) { diff --git a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetInternals.swift b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetInternals.swift index 5eb353409..eb36c5925 100644 --- a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetInternals.swift +++ b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetInternals.swift @@ -28,7 +28,7 @@ struct OrderedSetLayout: Hashable, CustomStringConvertible { func withOrderedSetLayouts( scales: [Int], - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, run body: (OrderedSetLayout) throws -> Void ) rethrows { diff --git a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift index b194d0174..bd10a6f88 100644 --- a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift +++ b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift @@ -913,7 +913,7 @@ class OrderedSetTests: CollectionTestCase { } func withSampleRanges( - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, _ body: (Range, Range) throws -> Void ) rethrows { diff --git a/Tests/RopeModuleTests/TestBigString.swift b/Tests/RopeModuleTests/TestBigString.swift index e198d0aab..450f5ba26 100644 --- a/Tests/RopeModuleTests/TestBigString.swift +++ b/Tests/RopeModuleTests/TestBigString.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -import XCTest +@preconcurrency import XCTest #if COLLECTIONS_SINGLE_MODULE import Collections #else @@ -17,7 +17,7 @@ import _CollectionsTestSupport import _RopeModule #endif -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +@available(SwiftStdlib 5.8, *) class TestBigString: CollectionTestCase { override var isAvailable: Bool { isRunningOnSwiftStdlib5_8 } @@ -138,7 +138,7 @@ class TestBigString: CollectionTestCase { func checkCharacterIndices( _ flat: String, _ big: BigString, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) -> (flat: [String.Index], big: [BigString.Index]) { // Check iterators var it1 = flat.makeIterator() @@ -188,7 +188,7 @@ class TestBigString: CollectionTestCase { func checkScalarIndices( _ flat: String, _ big: BigString, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) -> (flat: [String.Index], big: [BigString.Index]) { // Check iterators var it1 = flat.unicodeScalars.makeIterator() @@ -238,7 +238,7 @@ class TestBigString: CollectionTestCase { func checkUTF8Indices( _ flat: String, _ big: BigString, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) -> (flat: [String.Index], big: [BigString.Index]) { // Check iterators var it1 = flat.utf8.makeIterator() @@ -288,7 +288,7 @@ class TestBigString: CollectionTestCase { func checkUTF16Indices( _ flat: String, _ big: BigString, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) -> (flat: [String.Index], big: [BigString.Index]) { // Check iterators var it1 = flat.utf16.makeIterator() @@ -758,7 +758,7 @@ class TestBigString: CollectionTestCase { func check( _ indices: some Sequence, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var current = str.startIndex @@ -789,7 +789,7 @@ class TestBigString: CollectionTestCase { func check( _ indices: some Sequence, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var current = str.startIndex @@ -817,7 +817,7 @@ class TestBigString: CollectionTestCase { func check( _ indices: some Sequence, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var current = str.startIndex @@ -846,7 +846,7 @@ class TestBigString: CollectionTestCase { func check( _ indices: some Sequence, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var current = str.startIndex @@ -872,7 +872,7 @@ class TestBigString: CollectionTestCase { func check( _ indices: some Sequence, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var current = str.startIndex @@ -901,7 +901,7 @@ class TestBigString: CollectionTestCase { func check( _ indices: some Sequence, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var current = str.startIndex @@ -927,7 +927,7 @@ class TestBigString: CollectionTestCase { func check( _ indices: some Sequence, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var current = str.startIndex @@ -964,7 +964,7 @@ class TestBigString: CollectionTestCase { func check( _ indices: some Sequence, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var current = str.startIndex diff --git a/Tests/RopeModuleTests/TestRope.swift b/Tests/RopeModuleTests/TestRope.swift index e82c5752a..4736d32cb 100644 --- a/Tests/RopeModuleTests/TestRope.swift +++ b/Tests/RopeModuleTests/TestRope.swift @@ -505,7 +505,7 @@ class TestRope: CollectionTestCase { func checkEqual( _ x: Rope, _ y: [Int], - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { checkEqual(x, chunkify(y), file: file, line: line) @@ -514,7 +514,7 @@ class TestRope: CollectionTestCase { func checkEqual( _ x: Rope, _ y: [Chunk], - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { let u = Array(x) @@ -525,7 +525,7 @@ class TestRope: CollectionTestCase { _ a: Rope, _ b: [Int], range: Range, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var x = a diff --git a/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift b/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift index 67d084196..0beda4264 100644 --- a/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift +++ b/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift @@ -218,7 +218,7 @@ class SortedSetTests: CollectionTestCase { func withSampleRanges( - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, _ body: (Range, Range) throws -> Void ) rethrows { diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift new file mode 100644 index 000000000..b88327d7a --- /dev/null +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift @@ -0,0 +1,217 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import ContainersPreview +#endif + +#if compiler(>=6.2) +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 5.0, *) +public func expectContainerContents< + Element: Equatable, + C2: Collection, +>( + _ left: borrowing Span, + equalTo right: C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + var i = 0 + var it = right.makeIterator() + while i < left.count { + let a = left[i] + guard let b = it.next() else { + _expectFailure( + "\(left.testDescription) is longer than expected", + message, trapping: trapping, file: file, line: line) + return + } + guard a == b else { + _expectFailure( + "'\(a)' at index \(i) is not equal to '\(b)'", + message, trapping: trapping, file: file, line: line) + return + } + i += 1 + } + guard it.next() == nil else { + _expectFailure( + "\(left.testDescription) is shorter than expected", + message, trapping: trapping, file: file, line: line) + return + } +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 5.0, *) +public func expectContainerContents< + E1: ~Copyable, + C2: Collection, +>( + _ left: borrowing Span, + equivalentTo right: C2, + by areEquivalent: (borrowing E1, C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + var i = 0 + var it = right.makeIterator() + while i < left.count { + guard let b = it.next() else { + _expectFailure( + "\(left.testDescription) is longer than expected", + message, trapping: trapping, file: file, line: line) + return + } + guard areEquivalent(left[i], b) else { + _expectFailure( + "Element at index \(i) is not equal to '\(b)'", + message, trapping: trapping, file: file, line: line) + return + } + i += 1 + } + guard it.next() == nil else { + _expectFailure( + "\(left.testDescription) is shorter than expected", + message, trapping: trapping, file: file, line: line) + return + } +} + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +@available(SwiftStdlib 6.2, *) +public func expectContainersWithEquivalentElements< + C1: Container & ~Copyable & ~Escapable, + C2: Container & ~Copyable & ~Escapable +>( + _ left: borrowing C1, + _ right: borrowing C2, + by areEquivalent: (borrowing C1.Element, borrowing C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + if left.elementsEqual(right, by: areEquivalent) { return } + _expectFailure( + "Containers do not have equivalent elements", + message, trapping: trapping, file: file, line: line) +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 6.2, *) +public func expectContainersWithEqualElements< + Element: Equatable, + C1: Container & ~Copyable & ~Escapable, + C2: Container & ~Copyable & ~Escapable, +>( + _ left: borrowing C1, + _ right: borrowing C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + if left.elementsEqual(right) { return } + _expectFailure( + "Containers do not have equal elements", + message, trapping: trapping, file: file, line: line) +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 6.2, *) +public func expectContainerContents< + Element: Equatable, + C1: Container & ~Copyable & ~Escapable, + C2: Collection, +>( + _ left: borrowing C1, + equalTo right: C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + var i = left.startIndex + var it = right.makeIterator() + while i < left.endIndex { + let a = left[i] + guard let b = it.next() else { + _expectFailure( + "Container is longer than expected", + message, trapping: trapping, file: file, line: line) + return + } + guard a == b else { + _expectFailure( + "'\(a)' at index \(i) is not equal to '\(b)'", + message, trapping: trapping, file: file, line: line) + return + } + left.formIndex(after: &i) + } + guard it.next() == nil else { + _expectFailure( + "Container is shorter than expected", + message, trapping: trapping, file: file, line: line) + return + } +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 6.2, *) +public func expectContainerContents< + C1: Container & ~Copyable & ~Escapable, + C2: Collection, +>( + _ left: borrowing C1, + equivalentTo right: C2, + by areEquivalent: (borrowing C1.Element, C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + var i = left.startIndex + var it = right.makeIterator() + while i < left.endIndex { + guard let b = it.next() else { + _expectFailure( + "Container is longer than expected", + message, trapping: trapping, file: file, line: line) + return + } + guard areEquivalent(left[i], b) else { + _expectFailure( + "Element at index \(i) is not equal to '\(b)'", + message, trapping: trapping, file: file, line: line) + return + } + left.formIndex(after: &i) + } + guard it.next() == nil else { + _expectFailure( + "Container is shorter than expected", + message, trapping: trapping, file: file, line: line) + return + } +} +#endif +#endif diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift index 7224be07c..403991ccb 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift @@ -15,7 +15,7 @@ import XCTest public func expectFailure( _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { let message = message() @@ -23,7 +23,7 @@ public func expectFailure( TestContext.currentTrace(message), file: file, line: line) if trapping { - fatalError(message, file: file, line: line) + fatalError(message, file: (file), line: line) } } @@ -51,7 +51,7 @@ public func expectTrue( _ value: Bool, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if value { return } @@ -64,7 +64,7 @@ public func expectFalse( _ value: Bool, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if !value { return } @@ -77,7 +77,7 @@ public func expectNil( _ value: Optional, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if value == nil { return } @@ -90,7 +90,7 @@ public func expectNotNil( _ value: Optional, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if value != nil { return } @@ -103,7 +103,7 @@ public func expectNotNil( _ value: Optional, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, _ handler: (T) throws -> Void = { _ in } ) rethrows { @@ -120,7 +120,7 @@ public func expectIdentical( _ left: T?, _ right: T?, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left === right { return } @@ -135,7 +135,7 @@ public func expectNotIdentical( _ left: T, _ right: T, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left !== right { return } @@ -149,7 +149,7 @@ public func expectEquivalent( by areEquivalent: (A, B) -> Bool, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if areEquivalent(left, right) { return } @@ -163,7 +163,7 @@ public func expectEquivalent( by areEquivalent: (A, B) -> Bool, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if let left = left, let right = right, areEquivalent(left, right) { return } @@ -179,7 +179,7 @@ public func expectEqual( _ left: T, _ right: T, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left == right { return } @@ -192,7 +192,7 @@ public func expectEqual( _ left: (key: Key, value: Value), _ right: (key: Key, value: Value), _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left == right { return } @@ -206,7 +206,7 @@ public func expectEqual( _ left: T?, _ right: T?, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left == right { return } @@ -221,7 +221,7 @@ public func expectNotEqual( _ left: T, _ right: T, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left != right { return } @@ -234,7 +234,7 @@ public func expectLessThan( _ left: T, _ right: T, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left < right { return } @@ -247,7 +247,7 @@ public func expectLessThanOrEqual( _ left: T, _ right: T, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left <= right { return } @@ -260,7 +260,7 @@ public func expectGreaterThan( _ left: T, _ right: T, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left > right { return } @@ -273,7 +273,7 @@ public func expectGreaterThanOrEqual( _ left: T, _ right: T, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { if left >= right { return } @@ -289,7 +289,7 @@ public func expectEqualElements( _ right: S2, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S1.Element == S2.Element, S1.Element: Equatable { let left = Array(left) @@ -306,7 +306,7 @@ public func expectEquivalentElements( by areEquivalent: (S1.Element, S2.Element) -> Bool, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { let left = Array(left) @@ -327,7 +327,7 @@ public func expectEqualElements< _ right: S2, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S1.Element == (key: A, value: B), S2.Element == (key: A, value: B) { let left = Array(left) @@ -346,7 +346,7 @@ public func expectMonotonicallyIncreasing( _ items: S, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element: Comparable { let items = Array(items) @@ -367,7 +367,7 @@ public func expectStrictlyMonotonicallyIncreasing( _ items: S, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element: Comparable { let items = Array(items) @@ -384,11 +384,12 @@ public func expectStrictlyMonotonicallyIncreasing( } } +// FIXME: Remove this in favor of the better one below public func expectThrows( _ expression: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, _ errorHandler: (Error) -> Void = { _ in } ) { @@ -403,6 +404,25 @@ public func expectThrows( } } +public func expectThrows( + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #filePath, + line: UInt = #line, + body: () throws -> T, + errorHandler: (Error) -> Void = { _ in } +) { + do { + let result = try body() + expectFailure("Expression did not throw" + + (T.self == Void.self ? "" : " (returned '\(result)' instead)"), + trapping: trapping, + file: file, line: line) + } catch { + errorHandler(error) + } +} + public func expectType(_ actual: T, _ expectedType: Any.Type) { expectTrue(type(of: actual) == expectedType) } diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Combinatorics.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Combinatorics.swift index d0c7ac9b2..3295ff17f 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/Combinatorics.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Combinatorics.swift @@ -14,7 +14,7 @@ public func withEvery( _ label: String, by generator: () -> Element?, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, run body: (Element) throws -> Void ) rethrows { @@ -38,7 +38,7 @@ public func withEvery( public func withEvery( _ label: String, in items: S, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, run body: (S.Element) throws -> Void ) rethrows { @@ -60,7 +60,7 @@ public func withEvery( public func withEveryRange( _ label: String, in bounds: Range, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, run body: (Range) throws -> Void ) rethrows where T.Stride == Int { @@ -104,7 +104,7 @@ public func withSome( _ label: String, in items: C, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, run body: (C.Element) throws -> Void ) rethrows { @@ -143,7 +143,7 @@ public func withSomeRanges( _ label: String, in bounds: Range, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, run body: (Range) throws -> Void ) rethrows where T.Stride == Int { @@ -169,7 +169,7 @@ public func withHiddenCopies( if enabled: Bool, of value: inout S, checker: (S) -> Void = { _ in }, - file: StaticString = #file, line: UInt = #line, + file: StaticString = #filePath, line: UInt = #line, _ body: (inout S) throws -> R ) rethrows -> R where S.Element: Equatable { guard enabled else { return try body(&value) } @@ -201,7 +201,7 @@ public func withHiddenCopies< if enabled: Bool, of value: inout S, checker: (S) -> Void = { _ in }, - file: StaticString = #file, line: UInt = #line, + file: StaticString = #filePath, line: UInt = #line, _ body: (inout S) throws -> R ) rethrows -> R where S.Element == (key: Key, value: Value) { guard enabled else { return try body(&value) } diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift b/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift index ad3b89af4..3b39ef7d7 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Collections open source project // -// Copyright (c) 2021 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2021 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -20,7 +20,7 @@ public final class TestContext { internal var _trace: [Entry] = [] // FIXME: This ought to be a thread-local variable. - internal static var _current: TestContext? + nonisolated(unsafe) internal static var _current: TestContext? public init() {} } @@ -59,7 +59,7 @@ extension TestContext { public init( label: String, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { self.label = label @@ -121,7 +121,7 @@ extension TestContext { @discardableResult public func push( _ label: String, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) -> Entry { return push(Entry(label: label, file: file, line: line)) @@ -157,7 +157,7 @@ extension TestContext { /// Assertion failure messages within the closure will include the specified information to aid with debugging. public func withTrace( _ label: String, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line, _ body: () throws -> R ) rethrows -> R { @@ -208,7 +208,7 @@ extension TestContext { @inline(never) public func debuggerBreak( _ message: String, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { XCTFail(message, file: file, line: line) @@ -241,7 +241,7 @@ extension TestContext { /// public func failIfTraceMatches( _ expectedTrace: String, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { // Filter for lines that match the regex " *- " diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift index d7c20c106..5fab35b78 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift @@ -44,7 +44,7 @@ public func checkBidirectionalCollection Bool, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element == C.Element { checkSequence( @@ -87,7 +87,7 @@ public func _checkBidirectionalCollection_indexOffsetBy< expectedContents: [C.Element], by areEquivalent: (C.Element, C.Element) -> Bool, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { var allIndices = collection._indicesByIndexAfter() @@ -140,7 +140,7 @@ public func _checkBidirectionalCollection Bool, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element == C.Element { let entry = TestContext.current.push("checkBidirectionalCollection", file: file, line: line) diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift index 80e95d012..2ba98b603 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift @@ -68,7 +68,7 @@ public func checkCollection( _ collection: C, expectedContents: Expected, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where C.Element: Equatable, Expected.Element == C.Element { let expectedContents = Array(expectedContents) @@ -104,7 +104,7 @@ public func checkCollection( expectedContents: Expected, by areEquivalent: (C.Element, C.Element) -> Bool, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where Expected.Element == C.Element { checkSequence( @@ -125,7 +125,7 @@ public func _checkCollection( expectedContents: Expected, by areEquivalent: (C.Element, C.Element) -> Bool, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where Expected.Element == C.Element { let entry = TestContext.current.push("checkCollection", file: file, line: line) diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift index 8bde8a7de..6826f8e12 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift @@ -49,7 +49,7 @@ extension ExpectedComparisonResult: CustomStringConvertible { public func checkComparable( sortedEquivalenceClasses: [[Instance]], maxSamples: Int? = nil, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) { let instances = sortedEquivalenceClasses.flatMap { $0 } // oracle[i] is the index of the equivalence class that contains instances[i]. @@ -72,7 +72,7 @@ public func checkComparable( _ instances: Instances, oracle: (Instances.Index, Instances.Index) -> ExpectedComparisonResult, maxSamples: Int? = nil, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) where Instances.Element: Comparable, Instances.Index == Int { checkEquatable( instances, @@ -89,7 +89,7 @@ public func checkComparable( public func checkComparable( expected: ExpectedComparisonResult, _ lhs: T, _ rhs: T, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) { checkComparable( [lhs, rhs], @@ -103,7 +103,7 @@ public func _checkComparable( _ instances: Instances, oracle: (Instances.Index, Instances.Index) -> ExpectedComparisonResult, maxSamples: Int? = nil, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) where Instances.Element: Comparable, Instances.Index == Int { let entry = TestContext.current.push("checkComparable", file: file, line: line) defer { TestContext.current.pop(entry) } diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift new file mode 100644 index 000000000..66efc525e --- /dev/null +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift @@ -0,0 +1,231 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +import XCTest +import ContainersPreview + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + internal func _indicesByIndexAfter() -> [Index] { + var result: [Index] = [] + var i = startIndex + while i != endIndex { + result.append(i) + i = index(after: i) + } + return result + } + + @inlinable + internal func _indicesByFormIndexAfter() -> [Index] { + var result: [Index] = [] + var i = startIndex + while i != endIndex { + result.append(i) + formIndex(after: &i) + } + return result + } +} + +@available(SwiftStdlib 6.2, *) +@inlinable +public func checkContainer< + C: Container & ~Copyable & ~Escapable, + Expected: Sequence +>( + _ container: borrowing C, + expectedContents: Expected, + file: StaticString = #file, + line: UInt = #line +) where C.Element: Equatable { + checkContainer( + container, + expectedContents: expectedContents, + by: ==, + file: file, line: line) +} + +@available(SwiftStdlib 6.2, *) +@inlinable +public func checkContainer< + C: Container & ~Copyable & ~Escapable, + Expected: Sequence +>( + _ container: borrowing C, + expectedContents: Expected, + by areEquivalent: (C.Element, C.Element) -> Bool, + file: StaticString = #file, + line: UInt = #line +) where C.Element: Equatable { + let entry = TestContext.current.push("checkContainer", file: file, line: line) + defer { TestContext.current.pop(entry) } + + let expectedContents = Array(expectedContents) + expectEqual(container.isEmpty, expectedContents.isEmpty) + let actualCount = container.count + expectEqual(actualCount, expectedContents.count) + + let validIndices = container._indicesByIndexAfter() + + expectEqual( + container._indicesByIndexAfter(), validIndices, + "Container does not have stable indices") + + // Check that `index(after:)` produces the same results as `formIndex(after:)` + do { + let indicesByFormIndexAfter = container._indicesByFormIndexAfter() + expectEqual(indicesByFormIndexAfter, validIndices) + } + + expectEqual(container.index(container.startIndex, offsetBy: container.count), container.endIndex) + + // Check contents using indexing. + let actualContents = validIndices.map { container[$0] } + expectEquivalentElements(actualContents, expectedContents, by: areEquivalent) + + let allIndices = validIndices + [container.endIndex] + do { + var last: C.Index? = nil + for index in allIndices { + if let last { + // The indices must be monotonically increasing. + expectGreaterThan( + index, last, + "Index \(index) is not greater than immediately preceding index \(last)") + + // Aligning a valid index must not change it. + let nearestDown = container.index(alignedDown: index) + expectEqual(nearestDown, index, "Aligning a valid index down must not change its position") + let nearestUp = container.index(alignedUp: index) + expectEqual(nearestUp, index, "Aligning a valid index up must not change its position") + } + last = index + } + } + + // Check the `Comparable` conformance of the Index type. + if C.Index.self != Int.self { + checkComparable(allIndices, oracle: { .comparing($0, $1) }) + } + + withEveryRange("range", in: 0 ..< expectedContents.count) { range in + let i = range.lowerBound + let j = range.upperBound + + // Check `index(_,offsetBy:)` + let e = container.index(allIndices[i], offsetBy: j - i) + expectEqual(e, allIndices[j]) + if j < expectedContents.count { + expectEquivalent(container[e], expectedContents[j], by: areEquivalent) + } + + // Check `distance(from:to:)` + let d = container.distance(from: allIndices[i], to: allIndices[j]) + expectEqual(d, j - i) + } + + // Check `formIndex(_,offsetBy:limitedBy:)` + let limits = Set([0, allIndices.count - 1, allIndices.count / 2]).sorted() + withEvery("limit", in: limits) { limit in + withEvery("i", in: 0 ..< allIndices.count) { i in + let max = allIndices.count - i + (limit >= i ? 2 : 0) + withEvery("delta", in: 0 ..< max) { delta in + let target = i + delta + var index = allIndices[i] + var d = delta + container.formIndex(&index, offsetBy: &d, limitedBy: allIndices[limit]) + if i > limit { + expectEqual(d, 0, "Nonzero remainder after jump opposite limit") + expectEqual(index, allIndices[target], "Jump opposite limit landed in wrong position") + } else if target <= limit { + expectEqual(d, 0, "Nonzero remainder after jump within limit") + expectEqual(index, allIndices[target], "Jump within limit landed in wrong position") + } else { + expectEqual(d, target - limit, "Unexpected remainder after jump beyond limit") + expectEqual(index, allIndices[limit], "Jump beyond limit landed in unexpected position") + } + } + } + } + + // Check that the spans seem plausibly sized and that the indices are monotonic. + let spanShapes: [(offsetRange: Range, indexRange: Range)] = { + var r: [(offsetRange: Range, indexRange: Range)] = [] + var pos = 0 + var index = container.startIndex + while true { + let origIndex = index + let origPos = pos + let span = container.span(after: &index) + pos += span.count + if span.isEmpty { + expectEqual(origIndex, container.endIndex) + expectEqual(index, origIndex, "span(after:) is not expected to move the end index") + break + } + expectGreaterThan( + index, origIndex, "span(after:) does not monotonically increase the index") + expectEqual( + index, allIndices[pos], "span(after:) does not advance the index by the size of the returned span") + r.append((origPos ..< pos, origIndex ..< index)) + } + return r + }() + expectEqual( + spanShapes.reduce(into: 0, { $0 += $1.offsetRange.count }), actualCount, + "Container's count does not match the sum of its spans") + + + // Check that the spans have stable sizes and the expected contents. + do { + var pos = 0 + var index = container.startIndex + var spanIndex = 0 + while true { + let span = container.span(after: &index) + if span.isEmpty { break } + expectEqual( + span.count, spanShapes[spanIndex].offsetRange.count, + "Container has nondeterministic span sizes") + expectEqual( + index, spanShapes[spanIndex].indexRange.upperBound, + "Container has nondeterministic span boundaries") + for i in 0 ..< span.count { + expectEqual(span[i], expectedContents[pos]) + pos += 1 + } + spanIndex += 1 + } + expectEqual(spanIndex, spanShapes.endIndex) + expectEqual(pos, expectedContents.count) + } + + // Check that we can get a span beginning at every index, and that it extends as much as possible. + do { + for spanIndex in spanShapes.indices { + let (offsetRange, indexRange) = spanShapes[spanIndex] + for pos in offsetRange { + let start = validIndices[pos] + var i = start + let span = container.span(after: &i) + + expectEqual(span.count, offsetRange.upperBound - pos, + "Unexpected span size at offset \(pos), index \(start)") + expectEqual(i, indexRange.upperBound, + "Unexpected span upper bound at offset \(pos), index \(start)") + } + } + } +} +#endif diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift index 4bc8586fc..1aa2610e9 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift @@ -15,7 +15,7 @@ public func checkEquatable( equivalenceClasses: [[Instance]], maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { let instances = equivalenceClasses.flatMap { $0 } @@ -27,7 +27,7 @@ public func checkEquatable( _ instances: C, oracle: (C.Index, C.Index) -> Bool, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where C.Element: Equatable { let indices = Array(instances.indices) @@ -39,7 +39,7 @@ public func checkEquatable( public func checkEquatable( expectedEqual: Bool, _ lhs: T, _ rhs: T, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) { checkEquatable( [lhs, rhs], @@ -51,14 +51,16 @@ public func checkEquatable( _ instances: [Instance], oracle: (Int, Int) -> Bool, maxSamples: Int? = nil, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { let entry = TestContext.current.push("checkEquatable", file: file, line: line) defer { TestContext.current.pop(entry) } // For each index (which corresponds to an instance being tested) track the // set of equal instances. - var transitivityScoreboard: [Box>] = instances.map { _ in Box([]) } + var transitivityScoreboard: [ClassBox>] = instances.map { _ in + ClassBox([]) + } withSomeRanges( "range", in: 0 ..< instances.count - 1, maxSamples: maxSamples diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift index 91c329a58..58bd36b4c 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift @@ -30,7 +30,7 @@ private func _hash(_ value: H, seed: Int? = nil) -> Int { /// a distinct equivalence class under `==`. public func checkHashable( equivalenceClasses: [[Instance]], - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) { let instances = equivalenceClasses.flatMap { $0 } // oracle[i] is the index of the equivalence class that contains instances[i]. @@ -43,7 +43,7 @@ public func checkHashable( public func checkHashable( expectedEqual: Bool, _ lhs: T, _ rhs: T, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) { checkHashable( [lhs, rhs], equalityOracle: { expectedEqual || $0 == $1 }, file: file, line: line) @@ -55,7 +55,7 @@ public func checkHashable( public func checkHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) where Instances.Element: Hashable { checkEquatable(instances, oracle: equalityOracle, file: file, line: line) _checkHashable(instances, equalityOracle: equalityOracle, file: file, line: line) @@ -66,7 +66,7 @@ public func checkHashable( public func _checkHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, - file: StaticString = #file, line: UInt = #line + file: StaticString = #filePath, line: UInt = #line ) where Instances.Element: Hashable { let entry = TestContext.current.push("checkHashable", file: file, line: line) defer { TestContext.current.pop(entry) } diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift index da6869502..245cb9eda 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift @@ -21,7 +21,7 @@ import XCTest public func checkSequence( _ sequenceGenerator: () -> S, expectedContents: Expected, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element == Expected.Element, S.Element: Equatable { checkSequence( @@ -36,7 +36,7 @@ public func checkSequence( _ sequenceGenerator: () -> S, expectedContents: Expected, by areEquivalent: (S.Element, S.Element) -> Bool, - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) where S.Element == Expected.Element { let entry = TestContext.current.push("checkSequence", file: file, line: line) diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift index 02b3dbba0..73498e0a0 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -29,9 +29,14 @@ public struct MinimalBidirectionalCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -51,6 +56,13 @@ extension MinimalBidirectionalCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalBidirectionalCollection: BidirectionalCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift index 50d0d9868..17b7aa332 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -28,9 +28,14 @@ public struct MinimalCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -50,6 +55,13 @@ extension MinimalCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalCollection: Collection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift index 24998e2be..7343e9ba9 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -30,9 +30,14 @@ public struct MinimalMutableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalMutableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalMutableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift index b3eedec23..c5604a811 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -30,9 +30,14 @@ public struct MinimalMutableRangeReplaceableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalMutableRangeReplaceableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalMutableRangeReplaceableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift index bf666b8f9..adda47051 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -26,9 +26,14 @@ public struct MinimalRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -48,6 +53,13 @@ extension MinimalRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift index 4a119be33..ff45b7222 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -30,9 +30,14 @@ public struct MinimalRangeReplaceableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalRangeReplaceableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalRangeReplaceableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift new file mode 100644 index 000000000..703990e21 --- /dev/null +++ b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import ContainersPreview +#endif + +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +/// A container type with user-defined contents and storage chunks. +/// Useful for testing. +public struct StaccatoContainer: ~Copyable { + internal let _contents: RigidArray + internal let _spanCounts: [Int] + internal let _modulus: Int + + public init(contents: consuming RigidArray, spanCounts: [Int]) { + // FIXME: Make this take an arbitrary consumable container + precondition(!spanCounts.isEmpty && spanCounts.allSatisfy { $0 > 0 }) + self._contents = contents + self._spanCounts = spanCounts + self._modulus = spanCounts.reduce(into: 0, { $0 += $1 }) + } +} + +public struct _StaccatoContainerIndex: Comparable { + internal var _offset: Int + + internal init(_offset: Int) { + self._offset = _offset + } + + public static func == (left: Self, right: Self) -> Bool { left._offset == right._offset } + public static func < (left: Self, right: Self) -> Bool { left._offset < right._offset } +} + +extension StaccatoContainer where Element: ~Copyable { + public typealias Index = _StaccatoContainerIndex // rdar://150240032 + + func _spanCoordinates(atOffset offset: Int) -> (spanIndex: Int, spanOffset: Int) { + precondition(offset >= 0 && offset <= _contents.count) + var remainder = offset % _modulus + var i = 0 + while i < _spanCounts.count { + let c = _spanCounts[i] + if remainder < c { break } + remainder -= c + i += 1 + } + return (i, remainder) + } +} + +extension StaccatoContainer where Element: ~Copyable { + public var isEmpty: Bool { _contents.isEmpty } + public var count: Int { _contents.count } + public var startIndex: Index { Index(_offset: 0) } + public var endIndex: Index { Index(_offset: _contents.count) } + public func index(after index: Index) -> Index { + precondition(index >= startIndex && index < endIndex) + return Index(_offset: index._offset + 1) + } + public func formIndex(after i: inout Index) { + i = index(after: i) + } + public func index(_ index: Index, offsetBy n: Int) -> Index { + precondition(index._offset >= 0 && index._offset <= _contents.count) + let offset = index._offset + n + precondition(offset >= 0 && offset <= _contents.count) + return Index(_offset: offset) + } + + public func distance(from start: Index, to end: Index) -> Int { + precondition(start >= startIndex && start <= endIndex) + precondition(end >= startIndex && end <= endIndex) + return end._offset - start._offset + } +} + +@available(SwiftStdlib 6.2, *) +extension StaccatoContainer: Container where Element: ~Copyable { + public func formIndex(_ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index) { + precondition(i >= startIndex && i <= endIndex) + _contents.formIndex(&i._offset, offsetBy: &distance, limitedBy: limit._offset) + } + + @lifetime(borrow self) + public func borrowElement(at index: Index) -> Future.Borrow { + precondition(index >= startIndex && index < endIndex, "Index out of bounds") + return _contents.borrowElement(at: index._offset) + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Index) -> Span { + precondition(index >= startIndex && index <= endIndex) + let span = _contents.span + let (spanIndex, spanOffset) = _spanCoordinates(atOffset: index._offset) + let c = _spanCounts[spanIndex] - spanOffset + let startOffset = index._offset + let endOffset = Swift.min(startOffset + c, span.count) + index._offset = endOffset + return span._extracting(startOffset ..< endOffset) + } +} +#endif diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift b/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift index 6e6d86668..291095a3b 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -13,23 +13,31 @@ struct _MinimalCollectionCore { var state: _CollectionState var elements: [Element] + let isContiguous: Bool var underestimatedCount: Int public init( context: TestContext, elements: S, + isContiguous: Bool, underestimatedCount: UnderestimatedCountBehavior? = nil ) where S.Element == Element { - self.init(context: context, elements: Array(elements), underestimatedCount: underestimatedCount) + self.init( + context: context, + elements: Array(elements), + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } public init( context: TestContext, elements: [Element], + isContiguous: Bool, underestimatedCount: UnderestimatedCountBehavior? = nil ) { self.state = _CollectionState(context: context, parent: nil, count: elements.count) self.elements = elements + self.isContiguous = isContiguous self.underestimatedCount = underestimatedCount?.value(forCount: elements.count) ?? elements.count } @@ -76,7 +84,7 @@ extension _MinimalCollectionCore { func assertValidIndex( _ index: MinimalIndex, _ message: @autoclosure () -> String = "Invalid index", - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { expectTrue(isValidIndex(index), @@ -87,7 +95,7 @@ extension _MinimalCollectionCore { func assertValidIndexBeforeEnd( _ index: MinimalIndex, _ message: @autoclosure () -> String = "Invalid index", - file: StaticString = #file, + file: StaticString = #filePath, line: UInt = #line ) { expectTrue(isValidIndex(index), diff --git a/Tests/_CollectionsTestSupport/Utilities/Box.swift b/Tests/_CollectionsTestSupport/Utilities/ClassBox.swift similarity index 84% rename from Tests/_CollectionsTestSupport/Utilities/Box.swift rename to Tests/_CollectionsTestSupport/Utilities/ClassBox.swift index 3141f17c6..a58500827 100644 --- a/Tests/_CollectionsTestSupport/Utilities/Box.swift +++ b/Tests/_CollectionsTestSupport/Utilities/ClassBox.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -public final class Box { +public final class ClassBox { public init(_ value: T) { self.value = value } public var value: T } diff --git a/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift b/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift new file mode 100644 index 000000000..b1b32aae8 --- /dev/null +++ b/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A type that tracks the number of live instances. +/// +/// `LifetimeTracked` is useful to check for leaks in algorithms and data +/// structures. The easiest way to produce instances is to use the +/// `withLifetimeTracking` function: +/// +/// class FooTests: XCTestCase { +/// func testFoo() { +/// withLifetimeTracking([1, 2, 3]) { instances in +/// _ = instances.sorted(by: >) +/// } +/// } +/// } +public struct LifetimeTrackedStruct: ~Copyable { + public let tracker: LifetimeTracker + internal var serialNumber: Int = 0 + public var payload: Payload + + public init(_ payload: consuming Payload, for tracker: LifetimeTracker) { + tracker.instances += 1 + tracker._nextSerialNumber += 1 + self.tracker = tracker + self.serialNumber = tracker._nextSerialNumber + self.payload = payload + } + + deinit { + precondition(serialNumber != 0, "Double deinit") + tracker.instances -= 1 + } +} + diff --git a/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift b/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift index 1728cff69..a938d3c2b 100644 --- a/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift +++ b/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -27,7 +27,7 @@ public class LifetimeTracker { check() } - public func check(file: StaticString = #file, line: UInt = #line) { + public func check(file: StaticString = #filePath, line: UInt = #line) { expectEqual(instances, 0, "Potential leak of \(instances) objects", file: file, line: line) @@ -37,6 +37,12 @@ public class LifetimeTracker { LifetimeTracked(payload, for: self) } + public func structInstance( + for payload: consuming Payload + ) -> LifetimeTrackedStruct { + LifetimeTrackedStruct(payload, for: self) + } + public func instances(for items: S) -> [LifetimeTracked] { return items.map { LifetimeTracked($0, for: self) } } @@ -49,11 +55,11 @@ public class LifetimeTracker { } @inlinable -public func withLifetimeTracking( - file: StaticString = #file, +public func withLifetimeTracking( + file: StaticString = #filePath, line: UInt = #line, - _ body: (LifetimeTracker) throws -> R -) rethrows -> R { + _ body: (LifetimeTracker) throws(E) -> R +) throws(E) -> R { let tracker = LifetimeTracker() defer { tracker.check(file: file, line: line) } return try body(tracker) diff --git a/Tests/_CollectionsTestSupport/Utilities/TestPrintable.swift b/Tests/_CollectionsTestSupport/Utilities/TestPrintable.swift new file mode 100644 index 000000000..a2ded08b0 --- /dev/null +++ b/Tests/_CollectionsTestSupport/Utilities/TestPrintable.swift @@ -0,0 +1,126 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import ContainersPreview +#endif + +#if compiler(>=6.2) +/// A silly variant of `CustomDebugStringConvertible` that supports noncopyable +/// and nonescapable conforming types. +/// +/// This is expected to be replaced by the stdlib's solution, once we have one. +/// (It can be as simple as generalizing the standard protocol, but for it serve +/// its purpose, we also need to be able to perform runtime conformance checks +/// on noncopyable/nonescapable entities (or otherwise detect and invoke this +/// property. Additionally, materializing a full `String` instance up front can +/// be too expensive in some environments -- we also need a solution that +/// streams descriptions into some caller-provided buffer, eliminating +/// memory overhead.) +public protocol TestPrintable: ~Copyable, ~Escapable { + var testDescription: String { get } +} + +extension _Pointer { + internal var _description: String { + let hex = String(UInt(bitPattern: self), radix: 16) + let len = MemoryLayout.size * 2 + return "0x" + String(repeating: "0", count: len - hex.count) + hex + } +} + +internal func _bufferDescription( + _ typename: String, start: P?, count: Int +) -> String { + "\(typename)(start: \(start?._description ?? "nil"), count: \(count))" +} + +extension UnsafeRawBufferPointer: TestPrintable { + public var testDescription: String { + _bufferDescription( + "UnsafeRawBufferPointer", start: baseAddress, count: count) + } +} +extension UnsafeMutableRawBufferPointer: TestPrintable { + public var testDescription: String { + _bufferDescription( + "UnsafeMutableRawBufferPointer", start: baseAddress, count: count) + } +} +extension UnsafeBufferPointer: TestPrintable +where Element: ~Copyable +{ + public var testDescription: String { + _bufferDescription("UnsafeBufferPointer", start: baseAddress, count: count) + } +} +extension UnsafeMutableBufferPointer: TestPrintable +where Element: ~Copyable +{ + public var testDescription: String { + _bufferDescription( + "UnsafeMutableBufferPointer", start: baseAddress, count: count) + } +} + +@available(SwiftStdlib 5.0, *) +extension Span: TestPrintable where Element: ~Copyable { + public var testDescription: String { + self.withUnsafeBufferPointer { buffer in + _bufferDescription("Span", start: buffer.baseAddress, count: buffer.count) + } + } +} +@available(SwiftStdlib 5.0, *) +extension MutableSpan: TestPrintable where Element: ~Copyable { + public var testDescription: String { + self.withUnsafeBufferPointer { buffer in + _bufferDescription("MutableSpan", start: buffer.baseAddress, count: buffer.count) + } + } +} +#if compiler(>=6.2) && (compiler(>=6.3) || !os(Windows)) // FIXME: [2025-08-17] Windows has no 6.2 snapshot with OutputSpan +@available(SwiftStdlib 5.0, *) +extension OutputSpan: TestPrintable where Element: ~Copyable { + public var testDescription: String { + let capacity = self.capacity + return self.span.withUnsafeBufferPointer { buffer in + """ + OutputSpan(\ + start: \(buffer.baseAddress?._description ?? "nil"), \ + capacity: \(capacity), \ + count: \(buffer.count)) + """ + } + } +} +#endif + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +@available(SwiftStdlib 5.0, *) +extension InputSpan: TestPrintable where Element: ~Copyable { + public var testDescription: String { + let capacity = self.capacity + return self.span.withUnsafeBufferPointer { buffer in + """ + InputSpan(\ + start: \(buffer.baseAddress?._description ?? "nil"), \ + capacity: \(capacity), \ + count: \(buffer.count)) + """ + } + } +} +#endif +#endif diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index 4573a53bb..6148a272d 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -7,6 +7,30 @@ objects = { /* Begin PBXBuildFile section */ + 7D48BCD12E51524200D7F6F7 /* InputSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCCB2E51524200D7F6F7 /* InputSpan.swift */; }; + 7D48BCD22E51524200D7F6F7 /* DynamicArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCC22E51524200D7F6F7 /* DynamicArray.swift */; }; + 7D48BCD32E51524200D7F6F7 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCC82E51524200D7F6F7 /* Box.swift */; }; + 7D48BCD42E51524200D7F6F7 /* DynamicArray+Experimental.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCC32E51524200D7F6F7 /* DynamicArray+Experimental.swift */; }; + 7D48BCD52E51524200D7F6F7 /* RigidArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCC42E51524200D7F6F7 /* RigidArray.swift */; }; + 7D48BCD62E51524200D7F6F7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCCD2E51524200D7F6F7 /* Shared.swift */; }; + 7D48BCD72E51524200D7F6F7 /* RigidArray+Experimental.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCC52E51524200D7F6F7 /* RigidArray+Experimental.swift */; }; + 7D48BCD82E51524200D7F6F7 /* Borrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCC72E51524200D7F6F7 /* Borrow.swift */; }; + 7D48BCD92E51524200D7F6F7 /* Inout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCCA2E51524200D7F6F7 /* Inout.swift */; }; + 7D48BCDC2E51554900D7F6F7 /* ClassBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCDB2E51554900D7F6F7 /* ClassBox.swift */; }; + 7D48BCDF2E51558700D7F6F7 /* TestPrintable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCDD2E51558700D7F6F7 /* TestPrintable.swift */; }; + 7D48BCE02E51558700D7F6F7 /* LifetimeTrackedStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCDE2E51558700D7F6F7 /* LifetimeTrackedStruct.swift */; }; + 7D48BCE92E5158A100D7F6F7 /* DynamicArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCE22E5158A100D7F6F7 /* DynamicArrayTests.swift */; }; + 7D48BCEA2E5158A100D7F6F7 /* ArrayLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCE12E5158A100D7F6F7 /* ArrayLayout.swift */; }; + 7D48BCEB2E5158A100D7F6F7 /* RigidArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCE32E5158A100D7F6F7 /* RigidArrayTests.swift */; }; + 7D48BCEC2E5158A100D7F6F7 /* InoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCE72E5158A100D7F6F7 /* InoutTests.swift */; }; + 7D48BCED2E5158A100D7F6F7 /* BorrowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCE52E5158A100D7F6F7 /* BorrowTests.swift */; }; + 7D48BCEE2E5158A100D7F6F7 /* BoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCE62E5158A100D7F6F7 /* BoxTests.swift */; }; + 7D48BCF02E51597200D7F6F7 /* Assertions+Containers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCEF2E51597200D7F6F7 /* Assertions+Containers.swift */; }; + 7D48BCF22E51599200D7F6F7 /* CheckContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCF12E51599200D7F6F7 /* CheckContainer.swift */; }; + 7D48BCF42E5159A300D7F6F7 /* StaccatoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCF32E5159A300D7F6F7 /* StaccatoContainer.swift */; }; + 7D48BCFE2E5182CE00D7F6F7 /* UnsafeRawBufferPointer+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCFD2E5182CE00D7F6F7 /* UnsafeRawBufferPointer+Extras.swift */; }; + 7D48BCFF2E5182CE00D7F6F7 /* UnsafeMutableRawBufferPointer+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCFC2E5182CE00D7F6F7 /* UnsafeMutableRawBufferPointer+Extras.swift */; }; + 7D48BD002E5182CE00D7F6F7 /* LifetimeOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D48BCFA2E5182CE00D7F6F7 /* LifetimeOverride.swift */; }; 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859129E4F74400B291CD /* BitArray+Shifts.swift */; }; 7D9B859829E4F74400B291CD /* BitArray+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859229E4F74400B291CD /* BitArray+Descriptions.swift */; }; 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859329E4F74400B291CD /* BitArray+LosslessStringConvertible.swift */; }; @@ -403,7 +427,6 @@ 7DEBDB8729CCE44A00ADC226 /* MinimalMutableRandomAccessCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB3D29CCE43600ADC226 /* MinimalMutableRandomAccessCollection.swift */; }; 7DEBDB8829CCE44A00ADC226 /* MinimalSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB3E29CCE43600ADC226 /* MinimalSequence.swift */; }; 7DEBDB8929CCE44A00ADC226 /* Combinatorics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2129CCE43600ADC226 /* Combinatorics.swift */; }; - 7DEBDB8A29CCE44A00ADC226 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2D29CCE43600ADC226 /* Box.swift */; }; 7DEBDB8B29CCE44A00ADC226 /* _CollectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB3B29CCE43600ADC226 /* _CollectionState.swift */; }; 7DEBDB8C29CCE44A00ADC226 /* MinimalIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB4529CCE43600ADC226 /* MinimalIterator.swift */; }; 7DEBDB8D29CCE44A00ADC226 /* RandomStableSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2629CCE43600ADC226 /* RandomStableSample.swift */; }; @@ -426,6 +449,36 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7D48BCC12E51524200D7F6F7 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 7D48BCC22E51524200D7F6F7 /* DynamicArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicArray.swift; sourceTree = ""; }; + 7D48BCC32E51524200D7F6F7 /* DynamicArray+Experimental.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DynamicArray+Experimental.swift"; sourceTree = ""; }; + 7D48BCC42E51524200D7F6F7 /* RigidArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RigidArray.swift; sourceTree = ""; }; + 7D48BCC52E51524200D7F6F7 /* RigidArray+Experimental.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RigidArray+Experimental.swift"; sourceTree = ""; }; + 7D48BCC72E51524200D7F6F7 /* Borrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Borrow.swift; sourceTree = ""; }; + 7D48BCC82E51524200D7F6F7 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; + 7D48BCC92E51524200D7F6F7 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 7D48BCCA2E51524200D7F6F7 /* Inout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inout.swift; sourceTree = ""; }; + 7D48BCCB2E51524200D7F6F7 /* InputSpan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSpan.swift; sourceTree = ""; }; + 7D48BCCD2E51524200D7F6F7 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; + 7D48BCDB2E51554900D7F6F7 /* ClassBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassBox.swift; sourceTree = ""; }; + 7D48BCDD2E51558700D7F6F7 /* TestPrintable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestPrintable.swift; sourceTree = ""; }; + 7D48BCDE2E51558700D7F6F7 /* LifetimeTrackedStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifetimeTrackedStruct.swift; sourceTree = ""; }; + 7D48BCE12E5158A100D7F6F7 /* ArrayLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayLayout.swift; sourceTree = ""; }; + 7D48BCE22E5158A100D7F6F7 /* DynamicArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicArrayTests.swift; sourceTree = ""; }; + 7D48BCE32E5158A100D7F6F7 /* RigidArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RigidArrayTests.swift; sourceTree = ""; }; + 7D48BCE52E5158A100D7F6F7 /* BorrowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorrowTests.swift; sourceTree = ""; }; + 7D48BCE62E5158A100D7F6F7 /* BoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxTests.swift; sourceTree = ""; }; + 7D48BCE72E5158A100D7F6F7 /* InoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InoutTests.swift; sourceTree = ""; }; + 7D48BCEF2E51597200D7F6F7 /* Assertions+Containers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Assertions+Containers.swift"; sourceTree = ""; }; + 7D48BCF12E51599200D7F6F7 /* CheckContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckContainer.swift; sourceTree = ""; }; + 7D48BCF32E5159A300D7F6F7 /* StaccatoContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaccatoContainer.swift; sourceTree = ""; }; + 7D48BCF52E51651900D7F6F7 /* Package@swift-6.0.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Package@swift-6.0.swift"; path = "../Package@swift-6.0.swift"; sourceTree = SOURCE_ROOT; }; + 7D48BCF62E5172CF00D7F6F7 /* LifetimeOverride.swift.gyb */ = {isa = PBXFileReference; lastKnownFileType = text; path = LifetimeOverride.swift.gyb; sourceTree = ""; }; + 7D48BCF82E5172CF00D7F6F7 /* UnsafeMutableRawBufferPointer+Extras.swift.gyb */ = {isa = PBXFileReference; lastKnownFileType = text; path = "UnsafeMutableRawBufferPointer+Extras.swift.gyb"; sourceTree = ""; }; + 7D48BCF92E5172CF00D7F6F7 /* UnsafeRawBufferPointer+Extras.swift.gyb */ = {isa = PBXFileReference; lastKnownFileType = text; path = "UnsafeRawBufferPointer+Extras.swift.gyb"; sourceTree = ""; }; + 7D48BCFA2E5182CE00D7F6F7 /* LifetimeOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifetimeOverride.swift; sourceTree = ""; }; + 7D48BCFC2E5182CE00D7F6F7 /* UnsafeMutableRawBufferPointer+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnsafeMutableRawBufferPointer+Extras.swift"; sourceTree = ""; }; + 7D48BCFD2E5182CE00D7F6F7 /* UnsafeRawBufferPointer+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnsafeRawBufferPointer+Extras.swift"; sourceTree = ""; }; 7D5A64D229CCE8CC00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D329CCEE9A00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D429CCEF1500CB2595 /* generate-sources.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "generate-sources.sh"; sourceTree = ""; }; @@ -838,7 +891,6 @@ 7DEBDB2A29CCE43600ADC226 /* DictionaryAPIChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictionaryAPIChecker.swift; sourceTree = ""; }; 7DEBDB2B29CCE43600ADC226 /* StringConvertibleValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringConvertibleValue.swift; sourceTree = ""; }; 7DEBDB2C29CCE43600ADC226 /* AllOnesRandomNumberGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllOnesRandomNumberGenerator.swift; sourceTree = ""; }; - 7DEBDB2D29CCE43600ADC226 /* Box.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; 7DEBDB2E29CCE43600ADC226 /* LifetimeTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LifetimeTracker.swift; sourceTree = ""; }; 7DEBDB2F29CCE43600ADC226 /* RepeatableRandomNumberGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepeatableRandomNumberGenerator.swift; sourceTree = ""; }; 7DEBDB3029CCE43600ADC226 /* SortedCollectionAPIChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortedCollectionAPIChecker.swift; sourceTree = ""; }; @@ -888,10 +940,56 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7D48BCC62E51524200D7F6F7 /* ArrayModule */ = { + isa = PBXGroup; + children = ( + 7D48BCC12E51524200D7F6F7 /* CMakeLists.txt */, + 7D48BCC22E51524200D7F6F7 /* DynamicArray.swift */, + 7D48BCC32E51524200D7F6F7 /* DynamicArray+Experimental.swift */, + 7D48BCC42E51524200D7F6F7 /* RigidArray.swift */, + 7D48BCC52E51524200D7F6F7 /* RigidArray+Experimental.swift */, + ); + path = ArrayModule; + sourceTree = ""; + }; + 7D48BCCE2E51524200D7F6F7 /* ContainersPreview */ = { + isa = PBXGroup; + children = ( + 7D48BCC72E51524200D7F6F7 /* Borrow.swift */, + 7D48BCC82E51524200D7F6F7 /* Box.swift */, + 7D48BCC92E51524200D7F6F7 /* CMakeLists.txt */, + 7D48BCCA2E51524200D7F6F7 /* Inout.swift */, + 7D48BCCB2E51524200D7F6F7 /* InputSpan.swift */, + 7D48BCCD2E51524200D7F6F7 /* Shared.swift */, + ); + path = ContainersPreview; + sourceTree = ""; + }; + 7D48BCE42E5158A100D7F6F7 /* ArrayTests */ = { + isa = PBXGroup; + children = ( + 7D48BCE12E5158A100D7F6F7 /* ArrayLayout.swift */, + 7D48BCE22E5158A100D7F6F7 /* DynamicArrayTests.swift */, + 7D48BCE32E5158A100D7F6F7 /* RigidArrayTests.swift */, + ); + path = ArrayTests; + sourceTree = ""; + }; + 7D48BCE82E5158A100D7F6F7 /* ContainersTests */ = { + isa = PBXGroup; + children = ( + 7D48BCE52E5158A100D7F6F7 /* BorrowTests.swift */, + 7D48BCE62E5158A100D7F6F7 /* BoxTests.swift */, + 7D48BCE72E5158A100D7F6F7 /* InoutTests.swift */, + ); + path = ContainersTests; + sourceTree = ""; + }; 7DE91B1C29CA6721004483EB = { isa = PBXGroup; children = ( 7DE921A929CA782F004483EB /* Package.swift */, + 7D48BCF52E51651900D7F6F7 /* Package@swift-6.0.swift */, 7DE921AB29CA782F004483EB /* CMakeLists.txt */, 7DE921A829CA782F004483EB /* CODE_OF_CONDUCT.md */, 7DE921A729CA782F004483EB /* CODEOWNERS */, @@ -919,6 +1017,8 @@ isa = PBXGroup; children = ( 7DE91F2229CA70F3004483EB /* InternalCollectionsUtilities */, + 7D48BCCE2E51524200D7F6F7 /* ContainersPreview */, + 7D48BCC62E51524200D7F6F7 /* ArrayModule */, 7DE91F3929CA70F3004483EB /* BitCollections */, 7DE91F7929CA70F3004483EB /* Collections */, 7DE91EB629CA70F3004483EB /* DequeModule */, @@ -1236,14 +1336,17 @@ 7DEBDAD329CBEE5300ADC226 /* autogenerated */, 7DEBDAE229CBEE5300ADC226 /* IntegerTricks */, 7DEBDADA29CBEE5300ADC226 /* UnsafeBitSet */, + 7DE91F2E29CA70F3004483EB /* CMakeLists.txt */, 7DE91F3029CA70F3004483EB /* _SortedCollection.swift */, 7DE91F3829CA70F3004483EB /* _UniqueCollection.swift */, 7DEBDAED29CBEE5300ADC226 /* Debugging.swift.gyb */, 7DEBDAD129CBEE5200ADC226 /* Descriptions.swift.gyb */, + 7D48BCF62E5172CF00D7F6F7 /* LifetimeOverride.swift.gyb */, 7DEBDAD229CBEE5200ADC226 /* RandomAccessCollection+Offsets.swift.gyb */, 7DEBDAD929CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift.gyb */, 7DEBDAEC29CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift.gyb */, - 7DE91F2E29CA70F3004483EB /* CMakeLists.txt */, + 7D48BCF82E5172CF00D7F6F7 /* UnsafeMutableRawBufferPointer+Extras.swift.gyb */, + 7D48BCF92E5172CF00D7F6F7 /* UnsafeRawBufferPointer+Extras.swift.gyb */, ); path = InternalCollectionsUtilities; sourceTree = ""; @@ -1504,8 +1607,10 @@ isa = PBXGroup; children = ( 7DEBDB1F29CCE43600ADC226 /* _CollectionsTestSupport */, + 7D48BCE42E5158A100D7F6F7 /* ArrayTests */, 7DE921DA29CA8575004483EB /* BitCollectionsTests */, 7DE921CB29CA8575004483EB /* CollectionsTestSupportTests */, + 7D48BCE82E5158A100D7F6F7 /* ContainersTests */, 7DE921F529CA8575004483EB /* DequeTests */, 7DE921D029CA8575004483EB /* HashTreeCollectionsTests */, 7DE921F229CA8575004483EB /* HeapTests */, @@ -1631,9 +1736,12 @@ children = ( 7DEBDAD829CBEE5300ADC226 /* Debugging.swift */, 7DEBDAD729CBEE5300ADC226 /* Descriptions.swift */, + 7D48BCFA2E5182CE00D7F6F7 /* LifetimeOverride.swift */, 7DEBDAD629CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift */, 7DEBDAD529CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift */, 7DEBDAD429CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift */, + 7D48BCFC2E5182CE00D7F6F7 /* UnsafeMutableRawBufferPointer+Extras.swift */, + 7D48BCFD2E5182CE00D7F6F7 /* UnsafeRawBufferPointer+Extras.swift */, ); path = autogenerated; sourceTree = ""; @@ -1696,10 +1804,11 @@ 7DEBDB2029CCE43600ADC226 /* AssertionContexts */ = { isa = PBXGroup; children = ( - 7DEBDB2129CCE43600ADC226 /* Combinatorics.swift */, - 7DEBDB2229CCE43600ADC226 /* TestContext.swift */, 7DEBDB2329CCE43600ADC226 /* Assertions.swift */, + 7D48BCEF2E51597200D7F6F7 /* Assertions+Containers.swift */, 7DEBDB2429CCE43600ADC226 /* CollectionTestCase.swift */, + 7DEBDB2129CCE43600ADC226 /* Combinatorics.swift */, + 7DEBDB2229CCE43600ADC226 /* TestContext.swift */, ); path = AssertionContexts; sourceTree = ""; @@ -1707,19 +1816,21 @@ 7DEBDB2529CCE43600ADC226 /* Utilities */ = { isa = PBXGroup; children = ( - 7DEBDB2629CCE43600ADC226 /* RandomStableSample.swift */, - 7DEBDB2729CCE43600ADC226 /* LifetimeTracked.swift */, + 7DEBDB2C29CCE43600ADC226 /* AllOnesRandomNumberGenerator.swift */, + 7D48BCDB2E51554900D7F6F7 /* ClassBox.swift */, + 7D48BCDD2E51558700D7F6F7 /* TestPrintable.swift */, + 7DEBDB2A29CCE43600ADC226 /* DictionaryAPIChecker.swift */, + 7DEBDB3129CCE43600ADC226 /* HashableBox.swift */, 7DEBDB2829CCE43600ADC226 /* IndexRangeCollection.swift */, 7DEBDB2929CCE43600ADC226 /* Integer Square Root.swift */, - 7DEBDB2A29CCE43600ADC226 /* DictionaryAPIChecker.swift */, - 7DEBDB2B29CCE43600ADC226 /* StringConvertibleValue.swift */, - 7DEBDB2C29CCE43600ADC226 /* AllOnesRandomNumberGenerator.swift */, - 7DEBDB2D29CCE43600ADC226 /* Box.swift */, + 7DEBDB2729CCE43600ADC226 /* LifetimeTracked.swift */, + 7D48BCDE2E51558700D7F6F7 /* LifetimeTrackedStruct.swift */, 7DEBDB2E29CCE43600ADC226 /* LifetimeTracker.swift */, + 7DEBDB2629CCE43600ADC226 /* RandomStableSample.swift */, 7DEBDB2F29CCE43600ADC226 /* RepeatableRandomNumberGenerator.swift */, - 7DEBDB3029CCE43600ADC226 /* SortedCollectionAPIChecker.swift */, - 7DEBDB3129CCE43600ADC226 /* HashableBox.swift */, 7DEBDB3229CCE43600ADC226 /* SetAPIChecker.swift */, + 7DEBDB3029CCE43600ADC226 /* SortedCollectionAPIChecker.swift */, + 7DEBDB2B29CCE43600ADC226 /* StringConvertibleValue.swift */, ); path = Utilities; sourceTree = ""; @@ -1727,12 +1838,13 @@ 7DEBDB3329CCE43600ADC226 /* ConformanceCheckers */ = { isa = PBXGroup; children = ( + 7DEBDB3929CCE43600ADC226 /* CheckBidirectionalCollection.swift */, 7DEBDB3429CCE43600ADC226 /* CheckCollection.swift */, - 7DEBDB3529CCE43600ADC226 /* CheckHashable.swift */, 7DEBDB3629CCE43600ADC226 /* CheckComparable.swift */, + 7D48BCF12E51599200D7F6F7 /* CheckContainer.swift */, 7DEBDB3729CCE43600ADC226 /* CheckEquatable.swift */, + 7DEBDB3529CCE43600ADC226 /* CheckHashable.swift */, 7DEBDB3829CCE43600ADC226 /* CheckSequence.swift */, - 7DEBDB3929CCE43600ADC226 /* CheckBidirectionalCollection.swift */, ); path = ConformanceCheckers; sourceTree = ""; @@ -1741,19 +1853,20 @@ isa = PBXGroup; children = ( 7DEBDB3B29CCE43600ADC226 /* _CollectionState.swift */, - 7DEBDB3C29CCE43600ADC226 /* MinimalRandomAccessCollection.swift */, - 7DEBDB3D29CCE43600ADC226 /* MinimalMutableRandomAccessCollection.swift */, - 7DEBDB3E29CCE43600ADC226 /* MinimalSequence.swift */, - 7DEBDB3F29CCE43600ADC226 /* MinimalMutableRangeReplaceableRandomAccessCollection.swift */, + 7DEBDB4629CCE43600ADC226 /* _MinimalCollectionCore.swift */, 7DEBDB4029CCE43600ADC226 /* MinimalBidirectionalCollection.swift */, - 7DEBDB4129CCE43600ADC226 /* MinimalIndex.swift */, - 7DEBDB4229CCE43600ADC226 /* ResettableValue.swift */, - 7DEBDB4329CCE43600ADC226 /* MinimalRangeReplaceableRandomAccessCollection.swift */, + 7DEBDB4829CCE43600ADC226 /* MinimalCollection.swift */, + 7DEBDB4729CCE43600ADC226 /* MinimalDecoder.swift */, 7DEBDB4429CCE43600ADC226 /* MinimalEncoder.swift */, + 7DEBDB4129CCE43600ADC226 /* MinimalIndex.swift */, 7DEBDB4529CCE43600ADC226 /* MinimalIterator.swift */, - 7DEBDB4629CCE43600ADC226 /* _MinimalCollectionCore.swift */, - 7DEBDB4729CCE43600ADC226 /* MinimalDecoder.swift */, - 7DEBDB4829CCE43600ADC226 /* MinimalCollection.swift */, + 7DEBDB3D29CCE43600ADC226 /* MinimalMutableRandomAccessCollection.swift */, + 7DEBDB3F29CCE43600ADC226 /* MinimalMutableRangeReplaceableRandomAccessCollection.swift */, + 7DEBDB3C29CCE43600ADC226 /* MinimalRandomAccessCollection.swift */, + 7DEBDB4329CCE43600ADC226 /* MinimalRangeReplaceableRandomAccessCollection.swift */, + 7DEBDB3E29CCE43600ADC226 /* MinimalSequence.swift */, + 7DEBDB4229CCE43600ADC226 /* ResettableValue.swift */, + 7D48BCF32E5159A300D7F6F7 /* StaccatoContainer.swift */, ); path = MinimalTypes; sourceTree = ""; @@ -1827,7 +1940,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1600; + LastUpgradeCheck = 2600; TargetAttributes = { 7DE91B2529CA6721004483EB = { CreatedOnToolsVersion = 14.3; @@ -1950,6 +2063,15 @@ 7DE9212A29CA70F4004483EB /* TreeSet+Extras.swift in Sources */, 7DE9209A29CA70F4004483EB /* Rope+Append.swift in Sources */, 7DE9203F29CA70F3004483EB /* OrderedSet+Partial SetAlgebra isDisjoint.swift in Sources */, + 7D48BCD12E51524200D7F6F7 /* InputSpan.swift in Sources */, + 7D48BCD22E51524200D7F6F7 /* DynamicArray.swift in Sources */, + 7D48BCD32E51524200D7F6F7 /* Box.swift in Sources */, + 7D48BCD42E51524200D7F6F7 /* DynamicArray+Experimental.swift in Sources */, + 7D48BCD52E51524200D7F6F7 /* RigidArray.swift in Sources */, + 7D48BCD62E51524200D7F6F7 /* Shared.swift in Sources */, + 7D48BCD72E51524200D7F6F7 /* RigidArray+Experimental.swift in Sources */, + 7D48BCD82E51524200D7F6F7 /* Borrow.swift in Sources */, + 7D48BCD92E51524200D7F6F7 /* Inout.swift in Sources */, 7DE9208629CA70F4004483EB /* BigString+LosslessStringConvertible.swift in Sources */, 7DE9204329CA70F3004483EB /* _HashTable+Bucket.swift in Sources */, 7DE9216A29CA70F4004483EB /* TreeDictionary.swift in Sources */, @@ -2080,6 +2202,9 @@ 7DE9213D29CA70F4004483EB /* TreeSet+SetAlgebra isSubset.swift in Sources */, 7DE9203029CA70F3004483EB /* OrderedSet+SubSequence.swift in Sources */, 7DE9215529CA70F4004483EB /* _RawHashNode+UnsafeHandle.swift in Sources */, + 7D48BCFE2E5182CE00D7F6F7 /* UnsafeRawBufferPointer+Extras.swift in Sources */, + 7D48BCFF2E5182CE00D7F6F7 /* UnsafeMutableRawBufferPointer+Extras.swift in Sources */, + 7D48BD002E5182CE00D7F6F7 /* LifetimeOverride.swift in Sources */, 7DE9209429CA70F4004483EB /* Rope+_UnmanagedLeaf.swift in Sources */, 7DE9208429CA70F4004483EB /* BigString+Equatable.swift in Sources */, 7DE9203729CA70F3004483EB /* OrderedSet+Partial SetAlgebra union.swift in Sources */, @@ -2205,9 +2330,11 @@ files = ( 7DEBDB7F29CCE44A00ADC226 /* CheckBidirectionalCollection.swift in Sources */, 7DE9221829CA8576004483EB /* HashTableTests.swift in Sources */, + 7D48BCF02E51597200D7F6F7 /* Assertions+Containers.swift in Sources */, 7DE9220C29CA8576004483EB /* Availability.swift in Sources */, 7DE9220029CA8576004483EB /* TreeDictionary Smoke Tests.swift in Sources */, 7DEBDB9029CCE44A00ADC226 /* TestContext.swift in Sources */, + 7D48BCF22E51599200D7F6F7 /* CheckContainer.swift in Sources */, 7DE9221429CA8576004483EB /* OrderedSet Diffing Tests.swift in Sources */, 7DEBDB1D29CBF6B200ADC226 /* RandomAccessCollection+Offsets.swift in Sources */, 7DEBDB8529CCE44A00ADC226 /* AllOnesRandomNumberGenerator.swift in Sources */, @@ -2254,7 +2381,7 @@ 7DE9220529CA8576004483EB /* TreeDictionary.Values Tests.swift in Sources */, 7DEBDB8D29CCE44A00ADC226 /* RandomStableSample.swift in Sources */, 7DEBDB7429CCE44A00ADC226 /* DictionaryAPIChecker.swift in Sources */, - 7DEBDB8A29CCE44A00ADC226 /* Box.swift in Sources */, + 7D48BCDC2E51554900D7F6F7 /* ClassBox.swift in Sources */, 7DEBDB8629CCE44A00ADC226 /* LifetimeTracker.swift in Sources */, 7DEBDB8229CCE44A00ADC226 /* RepeatableRandomNumberGenerator.swift in Sources */, 7DEBDB7129CCE44A00ADC226 /* MinimalEncoder.swift in Sources */, @@ -2270,6 +2397,12 @@ 7DEBDB8F29CCE44A00ADC226 /* CheckEquatable.swift in Sources */, 7DEBDB7729CCE44A00ADC226 /* HashableBox.swift in Sources */, 7DE9220E29CA8576004483EB /* SampleStrings.swift in Sources */, + 7D48BCE92E5158A100D7F6F7 /* DynamicArrayTests.swift in Sources */, + 7D48BCEA2E5158A100D7F6F7 /* ArrayLayout.swift in Sources */, + 7D48BCEB2E5158A100D7F6F7 /* RigidArrayTests.swift in Sources */, + 7D48BCEC2E5158A100D7F6F7 /* InoutTests.swift in Sources */, + 7D48BCED2E5158A100D7F6F7 /* BorrowTests.swift in Sources */, + 7D48BCEE2E5158A100D7F6F7 /* BoxTests.swift in Sources */, 7DEBDB9129CCE44A00ADC226 /* CollectionTestCase.swift in Sources */, 7DEBDB1829CBF69F00ADC226 /* _UnsafeBitSet+_Word.swift in Sources */, 7DE921B929CA81DC004483EB /* _SortedCollection.swift in Sources */, @@ -2282,6 +2415,7 @@ 7DE9220829CA8576004483EB /* BitSetTests.swift in Sources */, 7DEBDB7329CCE44A00ADC226 /* _MinimalCollectionCore.swift in Sources */, 7DE921C829CA81DC004483EB /* _UniqueCollection.swift in Sources */, + 7D48BCF42E5159A300D7F6F7 /* StaccatoContainer.swift in Sources */, 7DE9220629CA8576004483EB /* TreeSet Tests.swift in Sources */, 7DEBDB9229CCE44A00ADC226 /* StringConvertibleValue.swift in Sources */, 7DEBDB6F29CCE44A00ADC226 /* MinimalMutableRangeReplaceableRandomAccessCollection.swift in Sources */, @@ -2289,6 +2423,8 @@ 7DEBDB0E29CBF68900ADC226 /* Integer rank.swift in Sources */, 7DE9220729CA8576004483EB /* BitSet.Counted Tests.swift in Sources */, 7DE9220F29CA8576004483EB /* OrderedDictionary Tests.swift in Sources */, + 7D48BCDF2E51558700D7F6F7 /* TestPrintable.swift in Sources */, + 7D48BCE02E51558700D7F6F7 /* LifetimeTrackedStruct.swift in Sources */, 7DE9221029CA8576004483EB /* OrderedDictionary Utils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2336,7 +2472,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7DEBDB9729CCE4A600ADC226 /* CollectionsTests.xcconfig */; buildSettings = { - DEAD_CODE_STRIPPING = YES; }; name = Debug; }; @@ -2344,7 +2479,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7DEBDB9729CCE4A600ADC226 /* CollectionsTests.xcconfig */; buildSettings = { - DEAD_CODE_STRIPPING = YES; }; name = Release; }; diff --git a/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme b/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme index 88230e523..fa7ef84a9 100644 --- a/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme +++ b/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme @@ -1,6 +1,6 @@