From f9d7af97cd6e636aa45ad98df850a4db7dcc0fb4 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 26 Jul 2024 16:32:45 -0700 Subject: [PATCH] Swift 6 snapshot fixes (#252) * Swift 6 snapshot fixes - `@_unsafeInheritExecutor` is now a build failure, we can use `isolated #isolation` instead. - `swift-corelibs-foundation` doesn't yet have a release post-sendability audit, so we can use `UncheckedSendable` to traffic them for now. * wip * wip --- .../DependencyValues/Calendar.swift | 23 +- .../DependencyValues/Locale.swift | 23 +- .../DependencyValues/TimeZone.swift | 23 +- .../Dependencies/Internal/Deprecations.swift | 6 +- Sources/Dependencies/WithDependencies.swift | 329 ++++++++++++------ 5 files changed, 277 insertions(+), 127 deletions(-) diff --git a/Sources/Dependencies/DependencyValues/Calendar.swift b/Sources/Dependencies/DependencyValues/Calendar.swift index be4163e7..a8f2e4d1 100644 --- a/Sources/Dependencies/DependencyValues/Calendar.swift +++ b/Sources/Dependencies/DependencyValues/Calendar.swift @@ -18,11 +18,28 @@ extension DependencyValues { /// // Make assertions with model... /// ``` public var calendar: Calendar { - get { self[CalendarKey.self] } - set { self[CalendarKey.self] = newValue } + 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 + } } private enum CalendarKey: DependencyKey { - static let liveValue = Calendar.autoupdatingCurrent + #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 } } diff --git a/Sources/Dependencies/DependencyValues/Locale.swift b/Sources/Dependencies/DependencyValues/Locale.swift index 681bd5db..8064f3e8 100644 --- a/Sources/Dependencies/DependencyValues/Locale.swift +++ b/Sources/Dependencies/DependencyValues/Locale.swift @@ -29,11 +29,28 @@ extension DependencyValues { /// // Make assertions with model... /// ``` public var locale: Locale { - get { self[LocaleKey.self] } - set { self[LocaleKey.self] = newValue } + 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 + } } private enum LocaleKey: DependencyKey { - static let liveValue = Locale.autoupdatingCurrent + #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 } } diff --git a/Sources/Dependencies/DependencyValues/TimeZone.swift b/Sources/Dependencies/DependencyValues/TimeZone.swift index 255fb898..390c0cc7 100644 --- a/Sources/Dependencies/DependencyValues/TimeZone.swift +++ b/Sources/Dependencies/DependencyValues/TimeZone.swift @@ -17,11 +17,28 @@ extension DependencyValues { /// // Make assertions with model... /// ``` public var timeZone: TimeZone { - get { self[TimeZoneKey.self] } - set { self[TimeZoneKey.self] = newValue } + 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 + } } private enum TimeZoneKey: DependencyKey { - static let liveValue = TimeZone.autoupdatingCurrent + #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 } } diff --git a/Sources/Dependencies/Internal/Deprecations.swift b/Sources/Dependencies/Internal/Deprecations.swift index 9ca829e4..33eee6b9 100644 --- a/Sources/Dependencies/Internal/Deprecations.swift +++ b/Sources/Dependencies/Internal/Deprecations.swift @@ -112,7 +112,7 @@ extension DependencyValues { } @available(*, deprecated, message: "Use 'withDependencies' instead.") - public static func withValue( + public static func withValue( _ keyPath: WritableKeyPath, _ value: @autoclosure () -> Value, operation: () async throws -> R @@ -133,7 +133,7 @@ extension DependencyValues { } @available(*, deprecated, message: "Use 'withDependencies' instead.") - public static func withValues( + public static func withValues( _ updateValuesForOperation: (inout Self) throws -> Void, operation: () async throws -> R ) async rethrows -> R { @@ -149,7 +149,7 @@ extension DependencyValues { } @available(*, deprecated, message: "Use 'withDependencies' instead.") - public static func withTestValues( + public static func withTestValues( _ updateValuesForOperation: (inout Self) async throws -> Void, assert operation: () async throws -> R ) async rethrows -> R { diff --git a/Sources/Dependencies/WithDependencies.swift b/Sources/Dependencies/WithDependencies.swift index 0d78a6ae..90d7d9f3 100644 --- a/Sources/Dependencies/WithDependencies.swift +++ b/Sources/Dependencies/WithDependencies.swift @@ -39,45 +39,68 @@ public func withDependencies( } } -/// Updates the current dependencies for the duration of an asynchronous operation. -/// -/// Any mutations made to ``DependencyValues`` inside `updateValuesForOperation` will be visible -/// to everything executed in the operation. For example, if you wanted to force the -/// ``DependencyValues/date`` dependency to be a particular date, you can do: -/// -/// ```swift -/// await withDependencies { -/// $0.date.now = Date(timeIntervalSince1970: 1234567890) -/// } operation: { -/// // References to date in here are pinned to 1234567890. -/// } -/// ``` -/// -/// - Parameters: -/// - updateValuesForOperation: A closure for updating the current dependency values for the -/// duration of the operation. -/// - operation: An operation to perform wherein dependencies have been overridden. -/// - Returns: The result returned from `operation`. -@_unsafeInheritExecutor -@discardableResult -public func withDependencies( - _ updateValuesForOperation: (inout DependencyValues) async throws -> Void, - operation: () async throws -> R -) async rethrows -> R { - try await isSetting(true) { - var dependencies = DependencyValues._current - try await updateValuesForOperation(&dependencies) - return try await DependencyValues.$_current.withValue(dependencies) { - try await isSetting(false) { - let result = try await operation() - if R.self is AnyClass { - dependencyObjects.store(result as AnyObject) +#if swift(>=6) + /// Updates the current dependencies for the duration of an asynchronous operation. + /// + /// Any mutations made to ``DependencyValues`` inside `updateValuesForOperation` will be visible + /// to everything executed in the operation. For example, if you wanted to force the + /// ``DependencyValues/date`` dependency to be a particular date, you can do: + /// + /// ```swift + /// await withDependencies { + /// $0.date.now = Date(timeIntervalSince1970: 1234567890) + /// } operation: { + /// // References to date in here are pinned to 1234567890. + /// } + /// ``` + /// + /// - Parameters: + /// - updateValuesForOperation: A closure for updating the current dependency values for the + /// duration of the operation. + /// - operation: An operation to perform wherein dependencies have been overridden. + /// - Returns: The result returned from `operation`. + @discardableResult + public func withDependencies( + isolation: isolated (any Actor)? = #isolation, + _ updateValuesForOperation: (inout DependencyValues) async throws -> Void, + operation: () async throws -> R + ) async rethrows -> R { + try await isSetting(true) { + var dependencies = DependencyValues._current + try await updateValuesForOperation(&dependencies) + return try await DependencyValues.$_current.withValue(dependencies) { + try await isSetting(false) { + let result = try await operation() + if R.self is AnyClass { + dependencyObjects.store(result as AnyObject) + } + return result } - return result } } } -} +#else + @_unsafeInheritExecutor + @discardableResult + public func withDependencies( + _ updateValuesForOperation: (inout DependencyValues) async throws -> Void, + operation: () async throws -> R + ) async rethrows -> R { + try await isSetting(true) { + var dependencies = DependencyValues._current + try await updateValuesForOperation(&dependencies) + return try await DependencyValues.$_current.withValue(dependencies) { + try await isSetting(false) { + let result = try await operation() + if R.self is AnyClass { + dependencyObjects.store(result as AnyObject) + } + return result + } + } + } + } +#endif /// Updates the current dependencies for the duration of a synchronous operation by taking the /// dependencies tied to a given object. @@ -154,84 +177,145 @@ public func withDependencies( ) } -/// Updates the current dependencies for the duration of an asynchronous operation by taking the -/// dependencies tied to a given object. -/// -/// - Parameters: -/// - model: An object with dependencies. The given model should have at least one `@Dependency` -/// property, or should have been initialized and returned from a `withDependencies` -/// operation. -/// - updateValuesForOperation: A closure for updating the current dependency values for the -/// duration of the operation. -/// - operation: The operation to run with the updated dependencies. -/// - Returns: The result returned from `operation`. -@_unsafeInheritExecutor -@discardableResult -public func withDependencies( - from model: Model, - _ updateValuesForOperation: (inout DependencyValues) async throws -> Void, - operation: () async throws -> R, - fileID: StaticString = #fileID, - filePath: StaticString = #filePath, - line: UInt = #line, - column: UInt = #column -) async rethrows -> R { - guard let values = dependencyObjects.values(from: model) - else { - reportIssue( - """ - You are trying to propagate dependencies to a child model from a model with no \ - dependencies. To fix this, the given '\(Model.self)' must be returned from another \ - 'withDependencies' closure, or the class must hold at least one '@Dependency' property. - """, +#if swift(>=6) + /// Updates the current dependencies for the duration of an asynchronous operation by taking the + /// dependencies tied to a given object. + /// + /// - Parameters: + /// - model: An object with dependencies. The given model should have at least one `@Dependency` + /// property, or should have been initialized and returned from a `withDependencies` + /// operation. + /// - updateValuesForOperation: A closure for updating the current dependency values for the + /// duration of the operation. + /// - operation: The operation to run with the updated dependencies. + /// - Returns: The result returned from `operation`. + @discardableResult + public func withDependencies( + from model: Model, + isolation: (any Actor)? = #isolation, + _ updateValuesForOperation: (inout DependencyValues) async throws -> Void, + operation: () async throws -> R, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) async rethrows -> R { + guard let values = dependencyObjects.values(from: model) + else { + reportIssue( + """ + You are trying to propagate dependencies to a child model from a model with no \ + dependencies. To fix this, the given '\(Model.self)' must be returned from another \ + 'withDependencies' closure, or the class must hold at least one '@Dependency' property. + """, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + return try await operation() + } + return try await withDependencies { + $0 = values.merging(DependencyValues._current) + try await updateValuesForOperation(&$0) + } operation: { + let result = try await operation() + if R.self is AnyClass { + dependencyObjects.store(result as AnyObject) + } + return result + } + } + + /// Updates the current dependencies for the duration of an asynchronous operation by taking the + /// dependencies tied to a given object. + /// + /// - Parameters: + /// - model: An object with dependencies. The given model should have at least one `@Dependency` + /// property, or should have been initialized and returned from a `withDependencies` + /// operation. + /// - operation: The operation to run with the updated dependencies. + /// - Returns: The result returned from `operation`. + @discardableResult + public func withDependencies( + from model: Model, + isolation: (any Actor)? = #isolation, + operation: () async throws -> R, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) async rethrows -> R { + try await withDependencies( + from: model, + { _ in }, + operation: operation, fileID: fileID, filePath: filePath, line: line, column: column ) - return try await operation() } - return try await withDependencies { - $0 = values.merging(DependencyValues._current) - try await updateValuesForOperation(&$0) - } operation: { - let result = try await operation() - if R.self is AnyClass { - dependencyObjects.store(result as AnyObject) +#else + @_unsafeInheritExecutor + @discardableResult + public func withDependencies( + from model: Model, + _ updateValuesForOperation: (inout DependencyValues) async throws -> Void, + operation: () async throws -> R, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) async rethrows -> R { + guard let values = dependencyObjects.values(from: model) + else { + reportIssue( + """ + You are trying to propagate dependencies to a child model from a model with no \ + dependencies. To fix this, the given '\(Model.self)' must be returned from another \ + 'withDependencies' closure, or the class must hold at least one '@Dependency' property. + """, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + return try await operation() + } + return try await withDependencies { + $0 = values.merging(DependencyValues._current) + try await updateValuesForOperation(&$0) + } operation: { + let result = try await operation() + if R.self is AnyClass { + dependencyObjects.store(result as AnyObject) + } + return result } - return result } -} -/// Updates the current dependencies for the duration of an asynchronous operation by taking the -/// dependencies tied to a given object. -/// -/// - Parameters: -/// - model: An object with dependencies. The given model should have at least one `@Dependency` -/// property, or should have been initialized and returned from a `withDependencies` -/// operation. -/// - operation: The operation to run with the updated dependencies. -/// - Returns: The result returned from `operation`. -@_unsafeInheritExecutor -@discardableResult -public func withDependencies( - from model: Model, - operation: () async throws -> R, - fileID: StaticString = #fileID, - filePath: StaticString = #filePath, - line: UInt = #line, - column: UInt = #column -) async rethrows -> R { - try await withDependencies( - from: model, - { _ in }, - operation: operation, - fileID: fileID, - filePath: filePath, - line: line, - column: column - ) -} + @_unsafeInheritExecutor + @discardableResult + public func withDependencies( + from model: Model, + operation: () async throws -> R, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) async rethrows -> R { + try await withDependencies( + from: model, + { _ in }, + operation: operation, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } +#endif /// Propagates the current dependencies to an escaping context. /// @@ -394,14 +478,29 @@ private func isSetting( #endif } -@_transparent -private func isSetting( - _ value: Bool, - operation: () async throws -> R -) async rethrows -> R { - #if DEBUG - try await DependencyValues.$isSetting.withValue(value, operation: operation) - #else - try await operation() - #endif -} +#if swift(>=6) + @_transparent + private func isSetting( + _ value: Bool, + isolation: isolated (any Actor)? = #isolation, + operation: () async throws -> R + ) async rethrows -> R { + #if DEBUG + try await DependencyValues.$isSetting.withValue(value, operation: operation) + #else + try await operation() + #endif + } +#else + @_transparent + private func isSetting( + _ value: Bool, + operation: () async throws -> R + ) async rethrows -> R { + #if DEBUG + try await DependencyValues.$isSetting.withValue(value, operation: operation) + #else + try await operation() + #endif + } +#endif