diff --git a/Makefile b/Makefile index 28b3926c..9feb66c4 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,8 @@ build-all-platforms: done; test-swift: - swift test - swift test -c release + swift test -Xswiftc -D -Xswiftc EXCLUDE_EXPORTS + swift test -c release -Xswiftc -D -Xswiftc EXCLUDE_EXPORTS test-linux: docker run \ diff --git a/Package.resolved b/Package.resolved index fb684bf9..47d1692a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,24 @@ { - "originHash" : "7f50c868512ea503c6f01eedfe44fa715482091f4425ceb4b41313dd601bb522", + "originHash" : "78636d694fb9f0167d899d76d37a5f6ae41810c557c442d816edadeaf19e317d", "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "dcccb979a2183b8df3334237e3dc1ae2b4116a86", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "cc46202b53476d64e824e0b6612da09d84ffde8e", + "version" : "1.0.6" + } + }, { "identity" : "swift-concurrency-extras", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 3feded97..a9308aa8 100644 --- a/Package.swift +++ b/Package.swift @@ -138,3 +138,20 @@ let package = Package( .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") ) #endif + +for target in package.targets { + target.swiftSettings = target.swiftSettings ?? [] + target.swiftSettings?.append(contentsOf: [ + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("ImmutableWeakCaptures"), + .enableUpcomingFeature("InferIsolatedConformances"), + .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("MemberImportVisibility"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + ]) + #if compiler(>=6.4) + target.swiftSettings?.append(contentsOf: [ + .treatAllWarnings(as: .error) + ]) + #endif +} diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift deleted file mode 100644 index 21d54c8e..00000000 --- a/Package@swift-5.9.swift +++ /dev/null @@ -1,118 +0,0 @@ -// swift-tools-version: 5.9 - -import CompilerPluginSupport -import PackageDescription - -let package = Package( - name: "swift-dependencies", - platforms: [ - .iOS(.v13), - .macOS(.v10_15), - .tvOS(.v13), - .watchOS(.v6), - ], - products: [ - .library( - name: "Dependencies", - targets: ["Dependencies"] - ), - .library( - name: "DependenciesMacros", - targets: ["DependenciesMacros"] - ), - ], - dependencies: [ - .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "1.0.2"), - .package(url: "https://github.com/pointfreeco/swift-clocks", from: "1.0.4"), - .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.0.0"), - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.4.0"), - .package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"605.0.0"), - ], - targets: [ - .target( - name: "DependenciesTestObserver", - dependencies: [ - .product(name: "IssueReporting", package: "xctest-dynamic-overlay") - ] - ), - .target( - name: "Dependencies", - dependencies: [ - .product(name: "Clocks", package: "swift-clocks"), - .product(name: "CombineSchedulers", package: "combine-schedulers"), - .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), - .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), - ], - swiftSettings: [ - .define("Clocks"), - .define("CombineSchedulers"), - .define("Foundation"), - ] - ), - .testTarget( - name: "DependenciesTests", - dependencies: [ - "Dependencies", - "DependenciesMacros", - .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), - .product(name: "IssueReportingTestSupport", package: "xctest-dynamic-overlay"), - ] - ), - .target( - name: "DependenciesMacros", - dependencies: [ - "DependenciesMacrosPlugin", - .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), - ] - ), - .macro( - name: "DependenciesMacrosPlugin", - dependencies: [ - .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), - ] - ), - ] -) - -#if !os(macOS) && !os(WASI) - package.products.append( - .library( - name: "DependenciesTestObserver", - type: .dynamic, - targets: ["DependenciesTestObserver"] - ) - ) -#endif - -#if !os(WASI) - package.dependencies.append(contentsOf: [ - .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0") - ]) - package.targets.append(contentsOf: [ - .testTarget( - name: "DependenciesMacrosPluginTests", - dependencies: [ - "DependenciesMacros", - "DependenciesMacrosPlugin", - .product(name: "MacroTesting", package: "swift-macro-testing"), - ] - ) - ]) -#endif - -#if !os(Windows) - // Add the documentation compiler plugin if possible - package.dependencies.append( - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") - ) -#endif - -for target in package.targets { - target.swiftSettings = target.swiftSettings ?? [] - target.swiftSettings?.append(contentsOf: [ - .enableExperimentalFeature("StrictConcurrency") - ]) -} diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 6ca38398..e280e695 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -123,3 +123,15 @@ let package = Package( .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") ) #endif + +for target in package.targets { + target.swiftSettings = target.swiftSettings ?? [] + target.swiftSettings?.append(contentsOf: [ + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("ImmutableWeakCaptures"), + .enableUpcomingFeature("InferIsolatedConformances"), + .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("MemberImportVisibility"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + ]) +} diff --git a/Sources/Dependencies/Dependency.swift b/Sources/Dependencies/Dependency.swift index 9dab9aa2..c7f0516e 100644 --- a/Sources/Dependencies/Dependency.swift +++ b/Sources/Dependencies/Dependency.swift @@ -1,5 +1,5 @@ #if canImport(SwiftUI) - import SwiftUI + public import SwiftUI #endif /// A property wrapper for accessing dependencies. @@ -76,7 +76,7 @@ public struct Dependency: _HasInitialValues { @Environment(\.dependencies) private var environmentValues #endif - private let keyPath: SendableKeyPath + private let keyPath: any SendableKeyPath private let filePath: StaticString private let fileID: StaticString private let line: UInt @@ -105,7 +105,7 @@ public struct Dependency: _HasInitialValues { /// - line: The source `#line` associated with the dependency. /// - column: The source `#column` associated with the dependency. public init( - _ keyPath: KeyPath & Sendable, + _ keyPath: any KeyPath & Sendable, fileID: StaticString = #fileID, filePath: StaticString = #filePath, line: UInt = #line, diff --git a/Sources/Dependencies/DependencyKey.swift b/Sources/Dependencies/DependencyKey.swift index 2e4a206a..70c2fc12 100644 --- a/Sources/Dependencies/DependencyKey.swift +++ b/Sources/Dependencies/DependencyKey.swift @@ -1,3 +1,5 @@ +import IssueReporting + /// A key for accessing dependencies. /// /// Types conform to this protocol to extend ``DependencyValues`` with custom dependencies. It is diff --git a/Sources/Dependencies/DependencyValues.swift b/Sources/Dependencies/DependencyValues.swift index 42e75b10..a1bea1b3 100644 --- a/Sources/Dependencies/DependencyValues.swift +++ b/Sources/Dependencies/DependencyValues.swift @@ -1,4 +1,4 @@ -import Foundation +public import Foundation import IssueReporting #if os(Windows) @@ -144,7 +144,7 @@ public struct DependencyValues: Sendable { guard let XCTestObservation = objc_getProtocol("XCTestObservation"), let XCTestObservationCenter = NSClassFromString("XCTestObservationCenter"), - let XCTestObservationCenter = XCTestObservationCenter as Any as? NSObjectProtocol, + let XCTestObservationCenter = XCTestObservationCenter as Any as? any NSObjectProtocol, let XCTestObservationCenterShared = XCTestObservationCenter .perform(Selector(("sharedTestObservationCenter")))? diff --git a/Sources/Dependencies/DependencyValues/Assert.swift b/Sources/Dependencies/DependencyValues/Assert.swift index 0c130a79..d42e1a9d 100644 --- a/Sources/Dependencies/DependencyValues/Assert.swift +++ b/Sources/Dependencies/DependencyValues/Assert.swift @@ -1,3 +1,5 @@ +import IssueReporting + extension DependencyValues { /// A dependency for handling assertions. /// diff --git a/Sources/Dependencies/DependencyValues/Calendar.swift b/Sources/Dependencies/DependencyValues/Calendar.swift index dec72c47..8c705a9f 100644 --- a/Sources/Dependencies/DependencyValues/Calendar.swift +++ b/Sources/Dependencies/DependencyValues/Calendar.swift @@ -1,47 +1,47 @@ #if Foundation -import Foundation + public import Foundation -extension DependencyValues { - /// The current calendar that features should use when handling dates. - /// - /// By default, the calendar returned from `Calendar.autoupdatingCurrent` is supplied. When used - /// in a testing context, access will call to `reportIssue` when invoked, unless explicitly - /// overridden using ``withDependencies(_:operation:)-4uz6m``: - /// - /// ```swift - /// // Provision model with overridden dependencies - /// let model = withDependencies { - /// $0.calendar = Calendar(identifier: .gregorian) - /// } operation: { - /// FeatureModel() - /// } - /// - /// // Make assertions with model... - /// ``` - public var calendar: Calendar { - get { - #if canImport(Darwin) - self[CalendarKey.self] - #else - self[CalendarKey.self].wrappedValue - #endif + extension DependencyValues { + /// The current calendar that features should use when handling dates. + /// + /// By default, the calendar returned from `Calendar.autoupdatingCurrent` is supplied. When used + /// in a testing context, access will call to `reportIssue` when invoked, unless explicitly + /// overridden using ``withDependencies(_:operation:)-4uz6m``: + /// + /// ```swift + /// // Provision model with overridden dependencies + /// let model = withDependencies { + /// $0.calendar = Calendar(identifier: .gregorian) + /// } operation: { + /// FeatureModel() + /// } + /// + /// // Make assertions with model... + /// ``` + public var calendar: Calendar { + get { + #if canImport(Darwin) + self[CalendarKey.self] + #else + self[CalendarKey.self].wrappedValue + #endif + } + set { + #if canImport(Darwin) + self[CalendarKey.self] = newValue + #else + self[CalendarKey.self].wrappedValue = newValue + #endif + } } - set { + + private enum CalendarKey: DependencyKey { #if canImport(Darwin) - self[CalendarKey.self] = newValue + static let liveValue = Calendar.autoupdatingCurrent #else - self[CalendarKey.self].wrappedValue = newValue + // NB: 'Calendar' sendability is not yet available in a 'swift-corelibs-foundation' release + static let liveValue = UncheckedSendable(Calendar.autoupdatingCurrent) #endif } } - - private enum CalendarKey: DependencyKey { - #if canImport(Darwin) - static let liveValue = Calendar.autoupdatingCurrent - #else - // NB: 'Calendar' sendability is not yet available in a 'swift-corelibs-foundation' release - static let liveValue = UncheckedSendable(Calendar.autoupdatingCurrent) - #endif - } -} #endif diff --git a/Sources/Dependencies/DependencyValues/Clocks.swift b/Sources/Dependencies/DependencyValues/Clocks.swift index ef0eb455..0dee4d04 100644 --- a/Sources/Dependencies/DependencyValues/Clocks.swift +++ b/Sources/Dependencies/DependencyValues/Clocks.swift @@ -1,82 +1,84 @@ #if Clocks -#if (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) - @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) - extension DependencyValues { - /// The current clock that features should use when a `ContinuousClock` would be appropriate. - /// - /// This clock is type-erased so that it can be swapped out in previews and tests for another - /// clock, like [`ImmediateClock`][immediate-clock] and [`TestClock`][test-clock] that come with - /// the [Clocks][swift-clocks] library (which is automatically imported and available when you - /// import this library). - /// - /// By default, a live `ContinuousClock` is supplied. When used in a testing context, an - /// [`UnimplementedClock`][unimplemented-clock] is provided, which generates an XCTest failure - /// when used, unless explicitly overridden using ``withDependencies(_:operation:)-4uz6m``: - /// - /// ```swift - /// // Provision model with overridden dependencies - /// let model = withDependencies { - /// $0.continuousClock = ImmediateClock() - /// } operation: { - /// FeatureModel() - /// } - /// - /// // Make assertions with model... - /// ``` - /// - /// See ``suspendingClock`` to override a feature's `SuspendingClock`, instead. - /// - /// [immediate-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/immediateclock/ - /// [test-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/testclock/ - /// [swift-clocks]: https://github.com/pointfreeco/swift-clocks - /// [unimplemented-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/unimplementedclock/ - public var continuousClock: any Clock { - get { self[ContinuousClockKey.self] } - set { self[ContinuousClockKey.self] = newValue } - } + import Clocks - /// The current clock that features should use when a `SuspendingClock` would be appropriate. - /// - /// This clock is type-erased so that it can be swapped out in previews and tests for another - /// clock, like [`ImmediateClock`][immediate-clock] and [`TestClock`][test-clock] that come with - /// the [Clocks][swift-clocks] library (which is automatically imported and available when you - /// import this library). - /// - /// By default, a live `SuspendingClock` is supplied. When used in a testing context, an - /// [`UnimplementedClock`][unimplemented-clock] is provided, which generates an XCTest failure - /// when used, unless explicitly overridden using ``withDependencies(_:operation:)-4uz6m``: - /// - /// ```swift - /// // Provision model with overridden dependencies - /// let model = withDependencies { - /// $0.suspendingClock = ImmediateClock() - /// } operation: { - /// FeatureModel() - /// } - /// - /// // Make assertions with model... - /// ``` - /// - /// See ``continuousClock`` to override a feature's `ContinuousClock`, instead. - /// - /// [immediate-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/immediateclock/ - /// [test-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/testclock/ - /// [swift-clocks]: https://github.com/pointfreeco/swift-clocks - /// [unimplemented-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/unimplementedclock/ - public var suspendingClock: any Clock { - get { self[SuspendingClockKey.self] } - set { self[SuspendingClockKey.self] = newValue } - } + #if (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) + @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) + extension DependencyValues { + /// The current clock that features should use when a `ContinuousClock` would be appropriate. + /// + /// This clock is type-erased so that it can be swapped out in previews and tests for another + /// clock, like [`ImmediateClock`][immediate-clock] and [`TestClock`][test-clock] that come + /// with the [Clocks][swift-clocks] library (which is automatically imported and available + /// when you import this library). + /// + /// By default, a live `ContinuousClock` is supplied. When used in a testing context, an + /// [`UnimplementedClock`][unimplemented-clock] is provided, which generates an XCTest failure + /// when used, unless explicitly overridden using ``withDependencies(_:operation:)-4uz6m``: + /// + /// ```swift + /// // Provision model with overridden dependencies + /// let model = withDependencies { + /// $0.continuousClock = ImmediateClock() + /// } operation: { + /// FeatureModel() + /// } + /// + /// // Make assertions with model... + /// ``` + /// + /// See ``suspendingClock`` to override a feature's `SuspendingClock`, instead. + /// + /// [immediate-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/immediateclock/ + /// [test-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/testclock/ + /// [swift-clocks]: https://github.com/pointfreeco/swift-clocks + /// [unimplemented-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/unimplementedclock/ + public var continuousClock: any Clock { + get { self[ContinuousClockKey.self] } + set { self[ContinuousClockKey.self] = newValue } + } - private enum ContinuousClockKey: DependencyKey { - static let liveValue: any Clock = ContinuousClock() - static let testValue: any Clock = UnimplementedClock(.continuous) - } + /// The current clock that features should use when a `SuspendingClock` would be appropriate. + /// + /// This clock is type-erased so that it can be swapped out in previews and tests for another + /// clock, like [`ImmediateClock`][immediate-clock] and [`TestClock`][test-clock] that come with + /// the [Clocks][swift-clocks] library (which is automatically imported and available when you + /// import this library). + /// + /// By default, a live `SuspendingClock` is supplied. When used in a testing context, an + /// [`UnimplementedClock`][unimplemented-clock] is provided, which generates an XCTest failure + /// when used, unless explicitly overridden using ``withDependencies(_:operation:)-4uz6m``: + /// + /// ```swift + /// // Provision model with overridden dependencies + /// let model = withDependencies { + /// $0.suspendingClock = ImmediateClock() + /// } operation: { + /// FeatureModel() + /// } + /// + /// // Make assertions with model... + /// ``` + /// + /// See ``continuousClock`` to override a feature's `ContinuousClock`, instead. + /// + /// [immediate-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/immediateclock/ + /// [test-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/testclock/ + /// [swift-clocks]: https://github.com/pointfreeco/swift-clocks + /// [unimplemented-clock]: https://pointfreeco.github.io/swift-clocks/main/documentation/clocks/unimplementedclock/ + public var suspendingClock: any Clock { + get { self[SuspendingClockKey.self] } + set { self[SuspendingClockKey.self] = newValue } + } - private enum SuspendingClockKey: DependencyKey { - static let liveValue: any Clock = SuspendingClock() - static let testValue: any Clock = UnimplementedClock(.suspending) + private enum ContinuousClockKey: DependencyKey { + static let liveValue: any Clock = ContinuousClock() + static let testValue: any Clock = UnimplementedClock(.continuous) + } + + private enum SuspendingClockKey: DependencyKey { + static let liveValue: any Clock = SuspendingClock() + static let testValue: any Clock = UnimplementedClock(.suspending) + } } - } -#endif + #endif #endif diff --git a/Sources/Dependencies/DependencyValues/Date.swift b/Sources/Dependencies/DependencyValues/Date.swift index afa6238f..94b53b1b 100644 --- a/Sources/Dependencies/DependencyValues/Date.swift +++ b/Sources/Dependencies/DependencyValues/Date.swift @@ -1,77 +1,77 @@ #if Foundation -import Foundation + public import Foundation -extension DependencyValues { - /// A dependency that returns the current date. - /// - /// By default, a "live" generator is supplied, which returns the current system date when called - /// by invoking `Date.init` under the hood. When used in tests, an "unimplemented" generator that - /// additionally reports test failures is supplied, unless explicitly overridden. - /// - /// You can access the current date from a feature by introducing a ``Dependency`` property - /// wrapper to the generator's ``DateGenerator/now`` property: - /// - /// ```swift - /// @Observable - /// final class FeatureModel { - /// @ObservationIgnored - /// @Dependency(\.date.now) var now - /// // ... - /// } - /// ``` - /// - /// To override the current date in tests, you can override the generator using - /// ``withDependencies(_:operation:)-4uz6m``: - /// - /// ```swift - /// // Provision model with overridden dependencies - /// let model = withDependencies { - /// $0.date.now = Date(timeIntervalSince1970: 1234567890) - /// } operation: { - /// FeatureModel() - /// } - /// - /// // Make assertions with model... - /// ``` - public var date: DateGenerator { - get { self[DateGeneratorKey.self] } - set { self[DateGeneratorKey.self] = newValue } - } + extension DependencyValues { + /// A dependency that returns the current date. + /// + /// By default, a "live" generator is supplied, which returns the current system date when + /// called by invoking `Date.init` under the hood. When used in tests, an "unimplemented" + /// generator that additionally reports test failures is supplied, unless explicitly overridden. + /// + /// You can access the current date from a feature by introducing a ``Dependency`` property + /// wrapper to the generator's ``DateGenerator/now`` property: + /// + /// ```swift + /// @Observable + /// final class FeatureModel { + /// @ObservationIgnored + /// @Dependency(\.date.now) var now + /// // ... + /// } + /// ``` + /// + /// To override the current date in tests, you can override the generator using + /// ``withDependencies(_:operation:)-4uz6m``: + /// + /// ```swift + /// // Provision model with overridden dependencies + /// let model = withDependencies { + /// $0.date.now = Date(timeIntervalSince1970: 1234567890) + /// } operation: { + /// FeatureModel() + /// } + /// + /// // Make assertions with model... + /// ``` + public var date: DateGenerator { + get { self[DateGeneratorKey.self] } + set { self[DateGeneratorKey.self] = newValue } + } - private enum DateGeneratorKey: DependencyKey { - static let liveValue = DateGenerator { Date() } + private enum DateGeneratorKey: DependencyKey { + static let liveValue = DateGenerator { Date() } + } } -} -/// A dependency that generates a date. -/// -/// See ``DependencyValues/date`` for more information. -public struct DateGenerator: Sendable { - private var generate: @Sendable () -> Date - - /// A generator that returns a constant date. + /// A dependency that generates a date. /// - /// - Parameter now: A date to return. - /// - Returns: A generator that always returns the given date. - public static func constant(_ now: Date) -> Self { - Self { now } - } + /// See ``DependencyValues/date`` for more information. + public struct DateGenerator: Sendable { + private var generate: @Sendable () -> Date - /// The current date. - public var now: Date { - get { self.generate() } - set { self.generate = { newValue } } - } + /// A generator that returns a constant date. + /// + /// - Parameter now: A date to return. + /// - Returns: A generator that always returns the given date. + public static func constant(_ now: Date) -> Self { + Self { now } + } - /// Initializes a date generator that generates a date from a closure. - /// - /// - Parameter generate: A closure that returns the current date when called. - public init(_ generate: @escaping @Sendable () -> Date) { - self.generate = generate - } + /// The current date. + public var now: Date { + get { self.generate() } + set { self.generate = { newValue } } + } + + /// Initializes a date generator that generates a date from a closure. + /// + /// - Parameter generate: A closure that returns the current date when called. + public init(_ generate: @escaping @Sendable () -> Date) { + self.generate = generate + } - public func callAsFunction() -> Date { - self.generate() + public func callAsFunction() -> Date { + self.generate() + } } -} #endif diff --git a/Sources/Dependencies/DependencyValues/Locale.swift b/Sources/Dependencies/DependencyValues/Locale.swift index cb86218c..63ea1bf4 100644 --- a/Sources/Dependencies/DependencyValues/Locale.swift +++ b/Sources/Dependencies/DependencyValues/Locale.swift @@ -1,60 +1,60 @@ #if Foundation -import Foundation + public import Foundation -extension DependencyValues { - /// The current locale that features should use. - /// - /// By default, the locale returned from `Locale.autoupdatingCurrent` is supplied. When used in - /// tests, access will call to `reportIssue` when invoked, unless explicitly overridden. - /// - /// You can access the current locale from a feature by introducing a ``Dependency`` property - /// wrapper to the property: - /// - /// ```swift - /// @Observable - /// final class FeatureModel { - /// @ObservationIgnored - /// @Dependency(\.locale) var locale - /// // ... - /// } - /// ``` - /// - /// To override the current locale in tests, use ``withValues(_:assert:)-1egh6``: + extension DependencyValues { + /// The current locale that features should use. + /// + /// By default, the locale returned from `Locale.autoupdatingCurrent` is supplied. When used in + /// tests, access will call to `reportIssue` when invoked, unless explicitly overridden. + /// + /// You can access the current locale from a feature by introducing a ``Dependency`` property + /// wrapper to the property: + /// + /// ```swift + /// @Observable + /// final class FeatureModel { + /// @ObservationIgnored + /// @Dependency(\.locale) var locale + /// // ... + /// } + /// ``` + /// + /// To override the current locale in tests, use ``withValues(_:assert:)-1egh6``: - /// ```swift - /// // Provision model with overridden dependencies - /// let model = withDependencies { - /// $0.locale = Locale(identifier: "en_US") - /// } operation: { - /// FeatureModel() - /// } - /// - /// // Make assertions with model... - /// ``` - public var locale: Locale { - get { - #if canImport(Darwin) - self[LocaleKey.self] - #else - self[LocaleKey.self].wrappedValue - #endif + /// ```swift + /// // Provision model with overridden dependencies + /// let model = withDependencies { + /// $0.locale = Locale(identifier: "en_US") + /// } operation: { + /// FeatureModel() + /// } + /// + /// // Make assertions with model... + /// ``` + public var locale: Locale { + get { + #if canImport(Darwin) + self[LocaleKey.self] + #else + self[LocaleKey.self].wrappedValue + #endif + } + set { + #if canImport(Darwin) + self[LocaleKey.self] = newValue + #else + self[LocaleKey.self].wrappedValue = newValue + #endif + } } - set { + + private enum LocaleKey: DependencyKey { #if canImport(Darwin) - self[LocaleKey.self] = newValue + static let liveValue = Locale.autoupdatingCurrent #else - self[LocaleKey.self].wrappedValue = newValue + // NB: 'Locale' sendability is not yet available in a 'swift-corelibs-foundation' release + static let liveValue = UncheckedSendable(Locale.autoupdatingCurrent) #endif } } - - private enum LocaleKey: DependencyKey { - #if canImport(Darwin) - static let liveValue = Locale.autoupdatingCurrent - #else - // NB: 'Locale' sendability is not yet available in a 'swift-corelibs-foundation' release - static let liveValue = UncheckedSendable(Locale.autoupdatingCurrent) - #endif - } -} #endif diff --git a/Sources/Dependencies/DependencyValues/MainQueue.swift b/Sources/Dependencies/DependencyValues/MainQueue.swift index 13edaf4d..6f1980a8 100644 --- a/Sources/Dependencies/DependencyValues/MainQueue.swift +++ b/Sources/Dependencies/DependencyValues/MainQueue.swift @@ -1,11 +1,14 @@ #if CombineSchedulers + public import CombineSchedulers + #if canImport(Combine) || canImport(OpenCombineShim) + public import Dispatch import Foundation #if canImport(Combine) import Combine #else - import OpenCombineShim + public import OpenCombineShim #endif extension DependencyValues { diff --git a/Sources/Dependencies/DependencyValues/MainRunLoop.swift b/Sources/Dependencies/DependencyValues/MainRunLoop.swift index f958bde2..5b9e3c67 100644 --- a/Sources/Dependencies/DependencyValues/MainRunLoop.swift +++ b/Sources/Dependencies/DependencyValues/MainRunLoop.swift @@ -1,11 +1,13 @@ #if CombineSchedulers + public import CombineSchedulers + #if canImport(Combine) || canImport(OpenCombineShim) - import Foundation + public import Foundation #if canImport(Combine) import Combine #else - import OpenCombineShim + public import OpenCombineShim #endif extension DependencyValues { diff --git a/Sources/Dependencies/DependencyValues/NotificationCenter.swift b/Sources/Dependencies/DependencyValues/NotificationCenter.swift index 98878045..7cbf3adc 100644 --- a/Sources/Dependencies/DependencyValues/NotificationCenter.swift +++ b/Sources/Dependencies/DependencyValues/NotificationCenter.swift @@ -1,33 +1,33 @@ #if Foundation -#if canImport(Foundation) && !os(WASI) - import Foundation + #if canImport(Foundation) && !os(WASI) + public import Foundation - extension DependencyValues { - /// The notification center that features should use. - /// - /// By default, `NotificationCenter.default` is provided. When used in tests, a task-local - /// center is provided, instead. - /// - /// You can access notification center from a feature by introducing a ``Dependency`` property - /// wrapper to the property: - /// - /// ```swift - /// @Observable - /// final class FeatureModel { - /// @ObservationIgnored - /// @Dependency(\.notificationCenter) var notificationCenter - /// // ... - /// } - /// ``` - public var notificationCenter: NotificationCenter { - get { self[NotificationCenterKey.self] } - set { self[NotificationCenterKey.self] = newValue } - } + extension DependencyValues { + /// The notification center that features should use. + /// + /// By default, `NotificationCenter.default` is provided. When used in tests, a task-local + /// center is provided, instead. + /// + /// You can access notification center from a feature by introducing a ``Dependency`` property + /// wrapper to the property: + /// + /// ```swift + /// @Observable + /// final class FeatureModel { + /// @ObservationIgnored + /// @Dependency(\.notificationCenter) var notificationCenter + /// // ... + /// } + /// ``` + public var notificationCenter: NotificationCenter { + get { self[NotificationCenterKey.self] } + set { self[NotificationCenterKey.self] = newValue } + } - private enum NotificationCenterKey: DependencyKey { - static let liveValue = NotificationCenter.default - static var testValue: NotificationCenter { NotificationCenter() } + private enum NotificationCenterKey: DependencyKey { + static let liveValue = NotificationCenter.default + static var testValue: NotificationCenter { NotificationCenter() } + } } - } -#endif + #endif #endif diff --git a/Sources/Dependencies/DependencyValues/OpenURL.swift b/Sources/Dependencies/DependencyValues/OpenURL.swift index 67dceef9..23d216d3 100644 --- a/Sources/Dependencies/DependencyValues/OpenURL.swift +++ b/Sources/Dependencies/DependencyValues/OpenURL.swift @@ -1,4 +1,6 @@ #if canImport(SwiftUI) + public import Foundation + import IssueReporting import SwiftUI extension DependencyValues { diff --git a/Sources/Dependencies/DependencyValues/TimeZone.swift b/Sources/Dependencies/DependencyValues/TimeZone.swift index 07a2a9f6..9960e8c2 100644 --- a/Sources/Dependencies/DependencyValues/TimeZone.swift +++ b/Sources/Dependencies/DependencyValues/TimeZone.swift @@ -1,46 +1,46 @@ #if Foundation -import Foundation + public import Foundation -extension DependencyValues { - /// The current time zone that features should use when handling dates. - /// - /// By default, the time zone returned from `TimeZone.autoupdatingCurrent` is supplied. When used - /// in tests, access will call to `reportIssue` when invoked, unless explicitly overridden: - /// - /// ```swift - /// // Provision model with overridden dependencies - /// let model = withDependencies { - /// $0.timeZone = TimeZone(secondsFromGMT: 0) - /// } operation: { - /// FeatureModel() - /// } - /// - /// // Make assertions with model... - /// ``` - public var timeZone: TimeZone { - get { - #if canImport(Darwin) - self[TimeZoneKey.self] - #else - self[TimeZoneKey.self].wrappedValue - #endif + extension DependencyValues { + /// The current time zone that features should use when handling dates. + /// + /// By default, the time zone returned from `TimeZone.autoupdatingCurrent` is supplied. When + /// used in tests, access will call to `reportIssue` when invoked, unless explicitly overridden: + /// + /// ```swift + /// // Provision model with overridden dependencies + /// let model = withDependencies { + /// $0.timeZone = TimeZone(secondsFromGMT: 0) + /// } operation: { + /// FeatureModel() + /// } + /// + /// // Make assertions with model... + /// ``` + public var timeZone: TimeZone { + get { + #if canImport(Darwin) + self[TimeZoneKey.self] + #else + self[TimeZoneKey.self].wrappedValue + #endif + } + set { + #if canImport(Darwin) + self[TimeZoneKey.self] = newValue + #else + self[TimeZoneKey.self].wrappedValue = newValue + #endif + } } - set { + + private enum TimeZoneKey: DependencyKey { #if canImport(Darwin) - self[TimeZoneKey.self] = newValue + static let liveValue = TimeZone.autoupdatingCurrent #else - self[TimeZoneKey.self].wrappedValue = newValue + // NB: 'TimeZone' sendability is not yet available in a 'swift-corelibs-foundation' release + static let liveValue = UncheckedSendable(TimeZone.autoupdatingCurrent) #endif } } - - private enum TimeZoneKey: DependencyKey { - #if canImport(Darwin) - static let liveValue = TimeZone.autoupdatingCurrent - #else - // NB: 'TimeZone' sendability is not yet available in a 'swift-corelibs-foundation' release - static let liveValue = UncheckedSendable(TimeZone.autoupdatingCurrent) - #endif - } -} #endif diff --git a/Sources/Dependencies/DependencyValues/URLSession.swift b/Sources/Dependencies/DependencyValues/URLSession.swift index de766de6..f06632a0 100644 --- a/Sources/Dependencies/DependencyValues/URLSession.swift +++ b/Sources/Dependencies/DependencyValues/URLSession.swift @@ -1,9 +1,9 @@ -#if FoundationNetworking -#if !os(WASI) - import Foundation +#if FoundationNetworking && !os(WASI) + public import Foundation + import IssueReporting #if canImport(FoundationNetworking) - import FoundationNetworking + public import FoundationNetworking #endif extension DependencyValues { @@ -28,9 +28,9 @@ /// ### API client dependencies /// /// While it is possible to use this dependency value from more complex dependencies, like API - /// clients, we generally advise against _designing_ a dependency around a URL session. Mocking - /// a URL session's responses is a complex process that requires a lot of work that can be - /// avoided. + /// clients, we generally advise against _designing_ a dependency around a URL session. + /// Mocking a URL session's responses is a complex process that requires a lot of work that + /// can be avoided. /// /// For example, instead of defining your dependency in a way that holds directly onto a URL /// session in order to invoke it from a concrete implementation: @@ -59,8 +59,8 @@ /// } /// ``` /// - /// Then, you can extend this type with a live implementation that uses a URL session under the - /// hood: + /// Then, you can extend this type with a live implementation that uses a URL session under + /// the hood: /// /// ```swift /// extension APIClient: DependencyKey { @@ -114,4 +114,3 @@ } } #endif -#endif diff --git a/Sources/Dependencies/DependencyValues/UUID.swift b/Sources/Dependencies/DependencyValues/UUID.swift index 98d9dbdf..bfe3f6f9 100644 --- a/Sources/Dependencies/DependencyValues/UUID.swift +++ b/Sources/Dependencies/DependencyValues/UUID.swift @@ -1,151 +1,152 @@ #if Foundation -import Foundation + import ConcurrencyExtras + public import Foundation -extension DependencyValues { - /// A dependency that generates UUIDs. - /// - /// Introduce controllable UUID generation to your features by using the ``Dependency`` property - /// wrapper with a key path to this property. The wrapped value is an instance of - /// ``UUIDGenerator``, which can be called with a closure to create UUIDs. (It can be called - /// directly because it defines ``UUIDGenerator/callAsFunction()``, which is called when you - /// invoke the instance as you would invoke a function.) - /// - /// For example, you could introduce controllable UUID generation to an observable object model - /// that creates to-dos with unique identifiers: - /// - /// ```swift - /// @Observable - /// final class TodosModel { - /// var todos: [Todo] = [] - /// - /// @ObservationIgnored - /// @Dependency(\.uuid) var uuid - /// - /// func addButtonTapped() { - /// todos.append(Todo(id: uuid())) - /// } - /// } - /// ``` - /// - /// By default, a "live" generator is supplied, which returns a random UUID when called by - /// invoking `UUID.init` under the hood. When used in tests, an "unimplemented" generator that - /// additionally reports test failures if invoked, unless explicitly overridden. - /// - /// To test a feature that depends on UUID generation, you can override its generator using - /// ``withDependencies(_:operation:)-4uz6m`` to override the underlying ``UUIDGenerator``: - /// - /// * ``UUIDGenerator/incrementing`` for reproducible UUIDs that count up from - /// `00000000-0000-0000-0000-000000000000`. - /// - /// * ``UUIDGenerator/constant(_:)`` for a generator that always returns the given UUID. - /// - /// For example, you could test the to-do-creating model by supplying an - /// ``UUIDGenerator/incrementing`` generator as a dependency: - /// - /// ```swift - /// @Test - /// func feature() { - /// let model = withDependencies { - /// $0.uuid = .incrementing - /// } operation: { - /// TodosModel() - /// } - /// - /// model.addButtonTapped() - /// #expect( - /// model.todos == [ - /// Todo(id: UUID(0)) - /// ] - /// ) - /// } - /// ``` - /// - /// > Note: This test uses the special ``Foundation/UUID/init(_:)`` UUID initializer that comes - /// with this library. - public var uuid: UUIDGenerator { - get { self[UUIDGeneratorKey.self] } - set { self[UUIDGeneratorKey.self] = newValue } - } + extension DependencyValues { + /// A dependency that generates UUIDs. + /// + /// Introduce controllable UUID generation to your features by using the ``Dependency`` property + /// wrapper with a key path to this property. The wrapped value is an instance of + /// ``UUIDGenerator``, which can be called with a closure to create UUIDs. (It can be called + /// directly because it defines ``UUIDGenerator/callAsFunction()``, which is called when you + /// invoke the instance as you would invoke a function.) + /// + /// For example, you could introduce controllable UUID generation to an observable object model + /// that creates to-dos with unique identifiers: + /// + /// ```swift + /// @Observable + /// final class TodosModel { + /// var todos: [Todo] = [] + /// + /// @ObservationIgnored + /// @Dependency(\.uuid) var uuid + /// + /// func addButtonTapped() { + /// todos.append(Todo(id: uuid())) + /// } + /// } + /// ``` + /// + /// By default, a "live" generator is supplied, which returns a random UUID when called by + /// invoking `UUID.init` under the hood. When used in tests, an "unimplemented" generator that + /// additionally reports test failures if invoked, unless explicitly overridden. + /// + /// To test a feature that depends on UUID generation, you can override its generator using + /// ``withDependencies(_:operation:)-4uz6m`` to override the underlying ``UUIDGenerator``: + /// + /// * ``UUIDGenerator/incrementing`` for reproducible UUIDs that count up from + /// `00000000-0000-0000-0000-000000000000`. + /// + /// * ``UUIDGenerator/constant(_:)`` for a generator that always returns the given UUID. + /// + /// For example, you could test the to-do-creating model by supplying an + /// ``UUIDGenerator/incrementing`` generator as a dependency: + /// + /// ```swift + /// @Test + /// func feature() { + /// let model = withDependencies { + /// $0.uuid = .incrementing + /// } operation: { + /// TodosModel() + /// } + /// + /// model.addButtonTapped() + /// #expect( + /// model.todos == [ + /// Todo(id: UUID(0)) + /// ] + /// ) + /// } + /// ``` + /// + /// > Note: This test uses the special ``Foundation/UUID/init(_:)`` UUID initializer that comes + /// with this library. + public var uuid: UUIDGenerator { + get { self[UUIDGeneratorKey.self] } + set { self[UUIDGeneratorKey.self] = newValue } + } - private enum UUIDGeneratorKey: DependencyKey { - static let liveValue = UUIDGenerator { UUID() } + private enum UUIDGeneratorKey: DependencyKey { + static let liveValue = UUIDGenerator { UUID() } + } } -} - -/// A dependency that generates a UUID. -/// -/// See ``DependencyValues/uuid`` for more information. -public struct UUIDGenerator: Sendable { - private let generate: @Sendable () -> UUID - /// A generator that returns a constant UUID. + /// A dependency that generates a UUID. /// - /// - Parameter uuid: A UUID to return. - /// - Returns: A generator that always returns the given UUID. - public static func constant(_ uuid: UUID) -> Self { - Self { uuid } - } + /// See ``DependencyValues/uuid`` for more information. + public struct UUIDGenerator: Sendable { + private let generate: @Sendable () -> UUID - /// A generator that generates UUIDs in incrementing order. - /// - /// For example: - /// - /// ```swift - /// let generate = UUIDGenerator.incrementing - /// generate() // UUID(00000000-0000-0000-0000-000000000000) - /// generate() // UUID(00000000-0000-0000-0000-000000000001) - /// generate() // UUID(00000000-0000-0000-0000-000000000002) - /// ``` - public static var incrementing: Self { - let generator = IncrementingUUIDGenerator() - return Self { generator() } - } + /// A generator that returns a constant UUID. + /// + /// - Parameter uuid: A UUID to return. + /// - Returns: A generator that always returns the given UUID. + public static func constant(_ uuid: UUID) -> Self { + Self { uuid } + } - /// Initializes a UUID generator that generates a UUID from a closure. - /// - /// - Parameter generate: A closure that returns the current date when called. - public init(_ generate: @escaping @Sendable () -> UUID) { - self.generate = generate - } + /// A generator that generates UUIDs in incrementing order. + /// + /// For example: + /// + /// ```swift + /// let generate = UUIDGenerator.incrementing + /// generate() // UUID(00000000-0000-0000-0000-000000000000) + /// generate() // UUID(00000000-0000-0000-0000-000000000001) + /// generate() // UUID(00000000-0000-0000-0000-000000000002) + /// ``` + public static var incrementing: Self { + let generator = IncrementingUUIDGenerator() + return Self { generator() } + } - public func callAsFunction() -> UUID { - self.generate() + /// Initializes a UUID generator that generates a UUID from a closure. + /// + /// - Parameter generate: A closure that returns the current date when called. + public init(_ generate: @escaping @Sendable () -> UUID) { + self.generate = generate + } + + public func callAsFunction() -> UUID { + self.generate() + } } -} -extension UUID { - /// Initializes a UUID from an integer by converting it to hex and padding it with 0's. - /// - /// For example: - /// - /// ```swift - /// UUID(16) == UUID(uuidString: "00000000-0000-0000-0000-000000000010") - /// ``` - /// - /// If a negative number is passed to this function then it is inverted and the negative sign - /// is encoded into the 16th bit of the UUID: - /// - /// ```swift - /// UUID(-16) == UUID(uuidString: "00000000-0000-0001-0000-000000000010") - /// 👆 - /// ``` - public init(_ intValue: Int) { - let isNegative = intValue < 0 - let intValue = isNegative ? -intValue : intValue - var hexString = String(format: "%016llx", intValue) - hexString.insert("-", at: hexString.index(hexString.startIndex, offsetBy: 4)) - self.init(uuidString: "00000000-0000-000\(isNegative ? "1" : "0")-\(hexString)")! + extension UUID { + /// Initializes a UUID from an integer by converting it to hex and padding it with 0's. + /// + /// For example: + /// + /// ```swift + /// UUID(16) == UUID(uuidString: "00000000-0000-0000-0000-000000000010") + /// ``` + /// + /// If a negative number is passed to this function then it is inverted and the negative sign + /// is encoded into the 16th bit of the UUID: + /// + /// ```swift + /// UUID(-16) == UUID(uuidString: "00000000-0000-0001-0000-000000000010") + /// 👆 + /// ``` + public init(_ intValue: Int) { + let isNegative = intValue < 0 + let intValue = isNegative ? -intValue : intValue + var hexString = String(format: "%016llx", intValue) + hexString.insert("-", at: hexString.index(hexString.startIndex, offsetBy: 4)) + self.init(uuidString: "00000000-0000-000\(isNegative ? "1" : "0")-\(hexString)")! + } } -} -private struct IncrementingUUIDGenerator: Sendable { - private let sequence = LockIsolated(0) + private struct IncrementingUUIDGenerator: Sendable { + private let sequence = LockIsolated(0) - func callAsFunction() -> UUID { - sequence.withValue { sequence in - defer { sequence += 1 } - return UUID(sequence) + func callAsFunction() -> UUID { + sequence.withValue { sequence in + defer { sequence += 1 } + return UUID(sequence) + } } } -} #endif diff --git a/Sources/Dependencies/DependencyValues/WithRandomNumberGenerator.swift b/Sources/Dependencies/DependencyValues/WithRandomNumberGenerator.swift index b7274d89..225b0e46 100644 --- a/Sources/Dependencies/DependencyValues/WithRandomNumberGenerator.swift +++ b/Sources/Dependencies/DependencyValues/WithRandomNumberGenerator.swift @@ -1,3 +1,4 @@ +import ConcurrencyExtras import Foundation extension DependencyValues { diff --git a/Sources/Dependencies/Internal/Deprecations.swift b/Sources/Dependencies/Internal/Deprecations.swift index f49748d5..c04f2a94 100644 --- a/Sources/Dependencies/Internal/Deprecations.swift +++ b/Sources/Dependencies/Internal/Deprecations.swift @@ -1,5 +1,7 @@ +public import ConcurrencyExtras + #if canImport(SwiftUI) - import SwiftUI + public import SwiftUI #endif // MARK: - Deprecated after 1.9.2 @@ -14,7 +16,7 @@ """ ) public static func dependency( - _ keyPath: WritableKeyPath & Sendable, + _ keyPath: any WritableKeyPath & Sendable, _ value: @autoclosure @escaping @Sendable () throws -> Value ) -> PreviewTrait { .dependencies { $0[keyPath: keyPath] = try value() } @@ -91,7 +93,7 @@ extension AsyncStream { } } -extension AsyncThrowingStream where Failure == Error { +extension AsyncThrowingStream where Failure == any Error { @available(*, deprecated, renamed: "makeStream(of:throwing:bufferingPolicy:)") @_documentation(visibility: private) public static func streamWithContinuation( @@ -152,7 +154,7 @@ extension AsyncStream where Element: Sendable { } } -extension AsyncThrowingStream where Element: Sendable, Failure == Error { +extension AsyncThrowingStream where Element: Sendable, Failure == any Error { @available( *, deprecated, diff --git a/Sources/Dependencies/Internal/Exports.swift b/Sources/Dependencies/Internal/Exports.swift index 6fe055e1..f0ca499d 100644 --- a/Sources/Dependencies/Internal/Exports.swift +++ b/Sources/Dependencies/Internal/Exports.swift @@ -1,9 +1,11 @@ -#if Clocks -@_exported import Clocks +#if !EXCLUDE_EXPORTS + #if Clocks + @_exported import Clocks + #endif + #if CombineSchedulers + @_exported import CombineSchedulers + #endif + @_exported import ConcurrencyExtras + @_exported import IssueReporting + @_exported import XCTestDynamicOverlay #endif -#if CombineSchedulers -@_exported import CombineSchedulers -#endif -@_exported import ConcurrencyExtras -@_exported import IssueReporting -@_exported import XCTestDynamicOverlay diff --git a/Sources/Dependencies/WithDependencies.swift b/Sources/Dependencies/WithDependencies.swift index 5cc14741..0f69d511 100644 --- a/Sources/Dependencies/WithDependencies.swift +++ b/Sources/Dependencies/WithDependencies.swift @@ -1,4 +1,6 @@ +import ConcurrencyExtras import Foundation +import IssueReporting /// Prepares global dependencies for the lifetime of your application. /// @@ -597,7 +599,7 @@ private final class DependencyObjects: Sendable { func values(from object: AnyObject) -> DependencyValues? { Mirror(reflecting: object).children .lazy - .compactMap({ $1 as? _HasInitialValues }) + .compactMap({ $1 as? any _HasInitialValues }) .first? .initialValues ?? self.storage.withValue({ [id = ObjectIdentifier(object)] in diff --git a/Sources/DependenciesMacros/Internal/Exports.swift b/Sources/DependenciesMacros/Internal/Exports.swift index 177b2a82..73d64ec6 100644 --- a/Sources/DependenciesMacros/Internal/Exports.swift +++ b/Sources/DependenciesMacros/Internal/Exports.swift @@ -1,2 +1,4 @@ -@_exported import IssueReporting -@_exported import XCTestDynamicOverlay +#if !EXCLUDE_EXPORTS + @_exported import IssueReporting + @_exported import XCTestDynamicOverlay +#endif diff --git a/Sources/DependenciesMacrosPlugin/DependencyClientMacro.swift b/Sources/DependenciesMacrosPlugin/DependencyClientMacro.swift index 208af33f..28c0bbb6 100644 --- a/Sources/DependenciesMacrosPlugin/DependencyClientMacro.swift +++ b/Sources/DependenciesMacrosPlugin/DependencyClientMacro.swift @@ -1,8 +1,8 @@ import SwiftDiagnostics import SwiftOperators -import SwiftSyntax +public import SwiftSyntax import SwiftSyntaxBuilder -import SwiftSyntaxMacros +public import SwiftSyntaxMacros #if !canImport(SwiftSyntax600) import SwiftSyntaxMacroExpansion diff --git a/Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift b/Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift index ab1d4137..dd2b0e99 100644 --- a/Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift +++ b/Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift @@ -1,9 +1,9 @@ import SwiftDiagnostics import SwiftOperators import SwiftParser -import SwiftSyntax +public import SwiftSyntax import SwiftSyntaxBuilder -import SwiftSyntaxMacros +public import SwiftSyntaxMacros #if !canImport(SwiftSyntax600) import SwiftSyntaxMacroExpansion diff --git a/Sources/DependenciesMacrosPlugin/Plugins.swift b/Sources/DependenciesMacrosPlugin/Plugins.swift index 28a7263d..dd24ea1b 100644 --- a/Sources/DependenciesMacrosPlugin/Plugins.swift +++ b/Sources/DependenciesMacrosPlugin/Plugins.swift @@ -3,7 +3,7 @@ import SwiftSyntaxMacros @main struct MacrosPlugin: CompilerPlugin { - let providingMacros: [Macro.Type] = [ + let providingMacros: [any Macro.Type] = [ DependencyClientMacro.self, DependencyEndpointMacro.self, DependencyEndpointIgnoredMacro.self, diff --git a/Sources/DependenciesTestSupport/Dependency+TestExtension.swift b/Sources/DependenciesTestSupport/Dependency+TestExtension.swift index 9d93c649..06a1b345 100644 --- a/Sources/DependenciesTestSupport/Dependency+TestExtension.swift +++ b/Sources/DependenciesTestSupport/Dependency+TestExtension.swift @@ -1,4 +1,4 @@ -import Dependencies +public import Dependencies extension Dependency { /// Creates a dependency property to read the specified key path and cast to a concrete type. @@ -34,7 +34,7 @@ extension Dependency { /// - line: The source `#line` associated with the dependency. /// - column: The source `#column` associated with the dependency. public init( - _ keyPath: KeyPath & Sendable, + _ keyPath: any KeyPath & Sendable, as type: Value.Type, fileID: StaticString = #fileID, filePath: StaticString = #filePath, diff --git a/Sources/DependenciesTestSupport/TestTrait.swift b/Sources/DependenciesTestSupport/TestTrait.swift index dc40e15c..503a1eac 100644 --- a/Sources/DependenciesTestSupport/TestTrait.swift +++ b/Sources/DependenciesTestSupport/TestTrait.swift @@ -1,7 +1,7 @@ #if canImport(Testing) && compiler(>=6) import ConcurrencyExtras - import Dependencies - import Testing + public import Dependencies + public import Testing #if compiler(>=6.1) @_documentation(visibility: private) @@ -14,7 +14,7 @@ public func provideScope( for test: Test, testCase: Test.Case?, - performing function: @Sendable () async throws -> Void + performing function: @concurrent () async throws -> Void ) async throws { try await withDependencies { if Self.isRoot { @@ -77,7 +77,7 @@ /// - keyPath: A key path to a dependency value. /// - value: A dependency value to override for the test. public static func dependency( - _ keyPath: WritableKeyPath & Sendable, + _ keyPath: any WritableKeyPath & Sendable, _ value: @autoclosure @escaping @Sendable () throws -> Value ) -> Self { Self { diff --git a/Tests/DependenciesMacrosPluginTests/DependencyClientMacroTests.swift b/Tests/DependenciesMacrosPluginTests/DependencyClientMacroTests.swift index 49a0524c..624a5381 100644 --- a/Tests/DependenciesMacrosPluginTests/DependencyClientMacroTests.swift +++ b/Tests/DependenciesMacrosPluginTests/DependencyClientMacroTests.swift @@ -1,5 +1,6 @@ import DependenciesMacrosPlugin import MacroTesting +import SnapshotTesting import XCTest final class DependencyClientMacroTests: BaseTestCase { diff --git a/Tests/DependenciesMacrosPluginTests/DependencyClientTests.swift b/Tests/DependenciesMacrosPluginTests/DependencyClientTests.swift index eb214502..9203958d 100644 --- a/Tests/DependenciesMacrosPluginTests/DependencyClientTests.swift +++ b/Tests/DependenciesMacrosPluginTests/DependencyClientTests.swift @@ -1,60 +1,61 @@ #if canImport(ObjectiveC) -import DependenciesMacros -import XCTest - -final class DependencyClientTests: BaseTestCase { - func testUnimplementedEndpoint() throws { - let client = Client() - - XCTExpectFailure { - $0.compactDescription == """ - failed - Unimplemented: 'Client.fetch' - """ + import DependenciesMacros + import IssueReporting + import XCTest + + final class DependencyClientTests: BaseTestCase { + func testUnimplementedEndpoint() throws { + let client = Client() + + XCTExpectFailure { + $0.compactDescription == """ + failed - Unimplemented: 'Client.fetch' + """ + } + + do { + let _ = try client.fetch() + XCTFail("Client.fetch should throw an error.") + } catch { + } } - do { - let _ = try client.fetch() - XCTFail("Client.fetch should throw an error.") - } catch { + func testUnimplementedWithNonThrowingEndpoint() { + let client = ClientWithNonThrowingEndpoint() + + XCTExpectFailure { + XCTAssertEqual(client.fetch(), 42) + } issueMatcher: { + $0.compactDescription == """ + failed - Unimplemented: \'ClientWithNonThrowingEndpoint.fetch\' + """ + } + + XCTExpectFailure { + XCTAssertEqual(client.fetchWithUnimplemented(), 42) + } issueMatcher: { + $0.compactDescription == """ + failed - Unimplemented: \'ClientWithNonThrowingEndpoint.fetchWithUnimplemented\' + """ + || $0.compactDescription == """ + failed - Unimplemented … + + Defined in 'ClientWithNonThrowingEndpoint' at: + DependenciesMacrosPluginTests/DependencyClientTests.swift:\(ClientWithNonThrowingEndpoint.line + 1) + """ + } } } - func testUnimplementedWithNonThrowingEndpoint() { - let client = ClientWithNonThrowingEndpoint() - - XCTExpectFailure { - XCTAssertEqual(client.fetch(), 42) - } issueMatcher: { - $0.compactDescription == """ - failed - Unimplemented: \'ClientWithNonThrowingEndpoint.fetch\' - """ - } + @DependencyClient + struct Client { + var fetch: () throws -> Int + } - XCTExpectFailure { - XCTAssertEqual(client.fetchWithUnimplemented(), 42) - } issueMatcher: { - $0.compactDescription == """ - failed - Unimplemented: \'ClientWithNonThrowingEndpoint.fetchWithUnimplemented\' - """ || - $0.compactDescription == """ - failed - Unimplemented … - - Defined in 'ClientWithNonThrowingEndpoint' at: - DependenciesMacrosPluginTests/DependencyClientTests.swift:\(ClientWithNonThrowingEndpoint.line + 1) - """ - } + @DependencyClient + struct ClientWithNonThrowingEndpoint { + var fetch: () -> Int = { 42 } + static let line = #line + var fetchWithUnimplemented: () -> Int = { unimplemented(placeholder: 42) } } -} - -@DependencyClient -struct Client { - var fetch: () throws -> Int -} - -@DependencyClient -struct ClientWithNonThrowingEndpoint { - var fetch: () -> Int = { 42 } - static let line = #line - var fetchWithUnimplemented: () -> Int = { unimplemented(placeholder: 42) } -} #endif diff --git a/Tests/DependenciesMacrosPluginTests/DependencyEndpointMacroTests.swift b/Tests/DependenciesMacrosPluginTests/DependencyEndpointMacroTests.swift index 1aaf1035..aecc7d5c 100644 --- a/Tests/DependenciesMacrosPluginTests/DependencyEndpointMacroTests.swift +++ b/Tests/DependenciesMacrosPluginTests/DependencyEndpointMacroTests.swift @@ -1,5 +1,6 @@ import DependenciesMacrosPlugin import MacroTesting +import SnapshotTesting import XCTest final class DependencyEndpointMacroTests: BaseTestCase { diff --git a/Tests/DependenciesMacrosPluginTests/DependencyEndpointTests.swift b/Tests/DependenciesMacrosPluginTests/DependencyEndpointTests.swift index e964ca2a..6ed9f6b2 100644 --- a/Tests/DependenciesMacrosPluginTests/DependencyEndpointTests.swift +++ b/Tests/DependenciesMacrosPluginTests/DependencyEndpointTests.swift @@ -1,6 +1,7 @@ #if canImport(DependenciesMacros) import Dependencies import DependenciesMacros + import IssueReporting import XCTest final class DependencyEndpointTests: XCTestCase { diff --git a/Tests/DependenciesMacrosPluginTests/MacroTests.swift b/Tests/DependenciesMacrosPluginTests/MacroTests.swift index eb2a7f42..e99df850 100644 --- a/Tests/DependenciesMacrosPluginTests/MacroTests.swift +++ b/Tests/DependenciesMacrosPluginTests/MacroTests.swift @@ -1,5 +1,6 @@ import Dependencies import DependenciesMacros +import IssueReporting private enum PackageACL { @DependencyClient diff --git a/Tests/DependenciesTests/BaseTestCaseTests.swift b/Tests/DependenciesTests/BaseTestCaseTests.swift index 6a910152..6f84fe1f 100644 --- a/Tests/DependenciesTests/BaseTestCaseTests.swift +++ b/Tests/DependenciesTests/BaseTestCaseTests.swift @@ -2,6 +2,9 @@ import Dependencies import XCTest final class BaseTestCaseTests: DerivedBaseTestCase { + #if compiler(>=6.2) + @concurrent + #endif override func setUp() async throws { try await super.setUp() XCTAssertEqual(DependencyValues._current.context, .test) @@ -32,6 +35,9 @@ final class BaseTestCaseTests: DerivedBaseTestCase { } class DerivedBaseTestCase: BaseTestCase { + #if compiler(>=6.2) + @concurrent + #endif override func setUp() async throws { try await super.setUp() XCTAssertEqual(DependencyValues._current.context, .test) @@ -49,6 +55,9 @@ class DerivedBaseTestCase: BaseTestCase { } class BaseTestCase: XCTestCase { + #if compiler(>=6.2) + @concurrent + #endif override func setUp() async throws { try await super.setUp() XCTAssertEqual(DependencyValues._current.context, .test) diff --git a/Tests/DependenciesTests/DependencyKeyTests.swift b/Tests/DependenciesTests/DependencyKeyTests.swift index b50b0e95..7a018664 100644 --- a/Tests/DependenciesTests/DependencyKeyTests.swift +++ b/Tests/DependenciesTests/DependencyKeyTests.swift @@ -1,4 +1,5 @@ import Dependencies +import IssueReporting import XCTest final class DependencyKeyTests: XCTestCase { diff --git a/Tests/DependenciesTests/DependencyTestExtensionTests.swift b/Tests/DependenciesTests/DependencyTestExtensionTests.swift index a12605bb..affe6ee1 100644 --- a/Tests/DependenciesTests/DependencyTestExtensionTests.swift +++ b/Tests/DependenciesTests/DependencyTestExtensionTests.swift @@ -1,5 +1,6 @@ #if compiler(>=6.2) && canImport(Testing) import Dependencies + import DependenciesTestSupport import Foundation import Testing @@ -14,7 +15,7 @@ @Dependency(ClientKey.self, as: TestClient.self) var client #expect(!client.isLive()) } - + @Test func keyPath() { @Dependency(\.client, as: TestClient.self) var client #expect(!client.isLive()) diff --git a/Tests/DependenciesTests/DependencyValuesTests.swift b/Tests/DependenciesTests/DependencyValuesTests.swift index 046841df..69dfc5b0 100644 --- a/Tests/DependenciesTests/DependencyValuesTests.swift +++ b/Tests/DependenciesTests/DependencyValuesTests.swift @@ -1,4 +1,6 @@ +import ConcurrencyExtras import Dependencies +import IssueReporting import XCTest final class DependencyValuesTests: XCTestCase { @@ -480,10 +482,10 @@ final class DependencyValuesTests: XCTestCase { } } - let model = await withDependencies { + let model = withDependencies { $0.fullDependency.value = 42 } operation: { - await FeatureModel() + FeatureModel() } await model.doSomething(expectation: expectation) @@ -510,7 +512,7 @@ final class DependencyValuesTests: XCTestCase { } } - let model = await FeatureModel() + let model = FeatureModel() await withDependencies { $0.fullDependency.value = 42 @@ -543,7 +545,7 @@ final class DependencyValuesTests: XCTestCase { } } - let model = await FeatureModel() + let model = FeatureModel() await withDependencies { $0.fullDependency.value = 42 @@ -570,7 +572,7 @@ final class DependencyValuesTests: XCTestCase { } } - let model = await FeatureModel() + let model = FeatureModel() await withDependencies { $0.fullDependency.value = 42 diff --git a/Tests/DependenciesTests/FireAndForgetTests.swift b/Tests/DependenciesTests/FireAndForgetTests.swift index aaf29c85..df23f73a 100644 --- a/Tests/DependenciesTests/FireAndForgetTests.swift +++ b/Tests/DependenciesTests/FireAndForgetTests.swift @@ -1,3 +1,4 @@ +import ConcurrencyExtras import Dependencies import XCTest @@ -70,13 +71,13 @@ final class FireAndForgetTests: XCTestCase { await self.fireAndForget(priority: .userInitiated) { @Dependency(\.date.now) var now: Date - await date.setValue(now) + date.setValue(now) } - while await date.value == nil { + while date.value == nil { await Task.yield() } - let value = await date.value + let value = date.value XCTAssertEqual(value, Date(timeIntervalSince1970: 1_234_567_890)) } } diff --git a/Tests/DependenciesTests/PrepareDependenciesTests.swift b/Tests/DependenciesTests/PrepareDependenciesTests.swift index d2fae1bf..1011fc36 100644 --- a/Tests/DependenciesTests/PrepareDependenciesTests.swift +++ b/Tests/DependenciesTests/PrepareDependenciesTests.swift @@ -1,5 +1,6 @@ #if canImport(Testing) import Dependencies + import DependenciesTestSupport import Foundation import Testing diff --git a/Tests/DependenciesTests/RootResettingTests.swift b/Tests/DependenciesTests/RootResettingTests.swift index 3a17d7ad..bf824508 100644 --- a/Tests/DependenciesTests/RootResettingTests.swift +++ b/Tests/DependenciesTests/RootResettingTests.swift @@ -1,5 +1,7 @@ #if compiler(>=6.1) && canImport(Testing) + import ConcurrencyExtras import Dependencies + import DependenciesTestSupport import Foundation import Testing