From e1f07facd96d4a843d321c33bc082875c00cade9 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 12 Aug 2024 08:24:30 -0700 Subject: [PATCH] Fix some warnings (#3279) We have a few warnings, mostly in the test suite, that have cropped up with deprecations and Xcode 16 strict concurrency, so let's address them. --- .../xcshareddata/swiftpm/Package.resolved | 20 ++--- .../SyncUpsTests/SyncUpFormTests.swift | 2 +- .../Internal/AssumeIsolated.swift | 30 +++++++ .../Internal/DispatchQueue.swift | 9 +- .../SharedState/Shared.swift | 10 ++- .../DebugTests.swift | 6 +- .../FileStorageTests.swift | 82 +++++++++---------- .../Reducers/ForEachReducerTests.swift | 1 + .../Reducers/StackReducerTests.swift | 1 + .../SharedTests.swift | 18 ++-- .../TaskResultTests.swift | 4 +- .../TestStoreNonExhaustiveTests.swift | 2 + 12 files changed, 107 insertions(+), 78 deletions(-) create mode 100644 Sources/ComposableArchitecture/Internal/AssumeIsolated.swift diff --git a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9e7b65556284..e6a0d68aaa52 100644 --- a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "aec6a73f5c1dc1f1be4f61888094b95cf995d973", - "version" : "1.3.2" + "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", + "version" : "1.3.3" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "cc26d06125dbc913c6d9e8a905a5db0b994509e0", - "version" : "1.3.5" + "revision" : "d7472be6b3c89251ce4c0db07d32405b43426781", + "version" : "1.3.7" } }, { @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-macro-testing", "state" : { - "revision" : "a35257b7e9ce44e92636447003a8eeefb77b145c", - "version" : "0.5.1" + "revision" : "20c1a8f3b624fb5d1503eadcaa84743050c350f4", + "version" : "0.5.2" } }, { @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", "state" : { - "revision" : "c097f955b4e724690f0fc8ffb7a6d4b881c9c4e3", - "version" : "1.17.2" + "revision" : "6d932a79e7173b275b96c600c86c603cf84f153c", + "version" : "1.17.4" } }, { @@ -140,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c", - "version" : "600.0.0-prerelease-2024-06-12" + "revision" : "06b5cdc432e93b60e3bdf53aff2857c6b312991a", + "version" : "600.0.0-prerelease-2024-07-30" } }, { diff --git a/Examples/SyncUps/SyncUpsTests/SyncUpFormTests.swift b/Examples/SyncUps/SyncUpsTests/SyncUpFormTests.swift index 66dc78759832..2789b84210f3 100644 --- a/Examples/SyncUps/SyncUpsTests/SyncUpFormTests.swift +++ b/Examples/SyncUps/SyncUpsTests/SyncUpFormTests.swift @@ -20,7 +20,7 @@ final class SyncUpFormTests: XCTestCase { $0.uuid = .incrementing } - XCTAssertNoDifference( + expectNoDifference( store.state.syncUp.attendees, [ Attendee(id: Attendee.ID(UUID(0))) diff --git a/Sources/ComposableArchitecture/Internal/AssumeIsolated.swift b/Sources/ComposableArchitecture/Internal/AssumeIsolated.swift new file mode 100644 index 000000000000..4a93c790f9e9 --- /dev/null +++ b/Sources/ComposableArchitecture/Internal/AssumeIsolated.swift @@ -0,0 +1,30 @@ +import Foundation + +extension MainActor { + // NB: This functionality was not back-deployed in Swift 5.9 + static func _assumeIsolated( + _ operation: @MainActor () throws -> T, + file: StaticString = #fileID, + line: UInt = #line + ) rethrows -> T { + #if swift(<5.10) + typealias YesActor = @MainActor () throws -> T + typealias NoActor = () throws -> T + + guard Thread.isMainThread else { + fatalError( + "Incorrect actor executor assumption; Expected same executor as \(self).", + file: file, + line: line + ) + } + + return try withoutActuallyEscaping(operation) { (_ fn: @escaping YesActor) throws -> T in + let rawFn = unsafeBitCast(fn, to: NoActor.self) + return try rawFn() + } + #else + return try assumeIsolated(operation, file: file, line: line) + #endif + } +} diff --git a/Sources/ComposableArchitecture/Internal/DispatchQueue.swift b/Sources/ComposableArchitecture/Internal/DispatchQueue.swift index 408fbd5f2656..489c1eb2701c 100644 --- a/Sources/ComposableArchitecture/Internal/DispatchQueue.swift +++ b/Sources/ComposableArchitecture/Internal/DispatchQueue.swift @@ -2,7 +2,7 @@ import Dispatch func mainActorASAP(execute block: @escaping @MainActor @Sendable () -> Void) { if DispatchQueue.getSpecific(key: key) == value { - assumeMainActorIsolated { + MainActor._assumeIsolated { block() } } else { @@ -18,10 +18,3 @@ private let key: DispatchSpecificKey = { return key }() private let value: UInt8 = 0 - -// NB: Currently we can't use 'MainActor.assumeIsolated' on CI, but we can approximate this in -// the meantime. -@MainActor(unsafe) -private func assumeMainActorIsolated(_ block: @escaping @MainActor @Sendable () -> Void) { - block() -} diff --git a/Sources/ComposableArchitecture/SharedState/Shared.swift b/Sources/ComposableArchitecture/SharedState/Shared.swift index 0f0d2690b478..d47c6031739d 100644 --- a/Sources/ComposableArchitecture/SharedState/Shared.swift +++ b/Sources/ComposableArchitecture/SharedState/Shared.swift @@ -218,12 +218,14 @@ public struct Shared { } try updateValueToExpectedResult(&snapshot) self.snapshot = snapshot - // TODO: Finesse error more than `XCTAssertNoDifference` - XCTAssertNoDifference( + // TODO: Finesse error more than `expectNoDifference` + expectNoDifference( self.currentValue, self.snapshot, - file: filePath, - line: line + fileID: fileID, + filePath: filePath, + line: line, + column: column ) self.snapshot = nil } diff --git a/Tests/ComposableArchitectureTests/DebugTests.swift b/Tests/ComposableArchitectureTests/DebugTests.swift index e75481bf30ac..c613dc961712 100644 --- a/Tests/ComposableArchitectureTests/DebugTests.swift +++ b/Tests/ComposableArchitectureTests/DebugTests.swift @@ -97,7 +97,7 @@ } store.send(true) try await Task.sleep(nanoseconds: 300_000_000) - XCTAssertNoDifference( + expectNoDifference( logs.value, """ - true @@ -129,7 +129,7 @@ store.send(true) store.send(false) _ = XCTWaiter.wait(for: [self.expectation(description: "wait")], timeout: 0.3) - XCTAssertNoDifference( + expectNoDifference( logs.value, """ - true @@ -177,7 +177,7 @@ } store.send(true) try await Task.sleep(nanoseconds: 300_000_000) - XCTAssertNoDifference( + expectNoDifference( logs.value, #""" DebugTests.State( diff --git a/Tests/ComposableArchitectureTests/FileStorageTests.swift b/Tests/ComposableArchitectureTests/FileStorageTests.swift index f3f7898fba7b..8792d6f5b47a 100644 --- a/Tests/ComposableArchitectureTests/FileStorageTests.swift +++ b/Tests/ComposableArchitectureTests/FileStorageTests.swift @@ -10,9 +10,9 @@ final class FileStorageTests: XCTestCase { $0.defaultFileStorage = .inMemory(fileSystem: fileSystem, scheduler: .immediate) } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() - XCTAssertNoDifference(fileSystem.value, [.fileURL: Data()]) + expectNoDifference(fileSystem.value, [.fileURL: Data()]) users.append(.blob) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) } } @@ -22,9 +22,9 @@ final class FileStorageTests: XCTestCase { $0.defaultFileStorage = .inMemory(fileSystem: fileSystem, scheduler: .immediate) } operation: { @Shared(.utf8String) var string = "" - XCTAssertNoDifference(fileSystem.value, [.utf8StringURL: Data()]) + expectNoDifference(fileSystem.value, [.utf8StringURL: Data()]) string = "hello" - XCTAssertNoDifference( + expectNoDifference( fileSystem.value[.utf8StringURL].map { String(decoding: $0, as: UTF8.self) }, "hello" ) @@ -41,25 +41,25 @@ final class FileStorageTests: XCTestCase { ) } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), nil) + try expectNoDifference(fileSystem.value.users(for: .fileURL), nil) users.append(.blob) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) users.append(.blobJr) testScheduler.advance(by: .seconds(1) - .milliseconds(1)) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) users.append(.blobSr) testScheduler.advance(by: .milliseconds(1)) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr, .blobSr]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr, .blobSr]) testScheduler.advance(by: .seconds(1)) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr, .blobSr]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr, .blobSr]) testScheduler.advance(by: .seconds(0.5)) users.append(.blobEsq) - try XCTAssertNoDifference( + try expectNoDifference( fileSystem.value.users(for: .fileURL), [ .blob, @@ -81,14 +81,14 @@ final class FileStorageTests: XCTestCase { ) } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), nil) + try expectNoDifference(fileSystem.value.users(for: .fileURL), nil) users.append(.blob) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) testScheduler.advance(by: .seconds(2)) users.append(.blobJr) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr]) } } @@ -104,15 +104,15 @@ final class FileStorageTests: XCTestCase { ) } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), nil) + try expectNoDifference(fileSystem.value.users(for: .fileURL), nil) users.append(.blob) users.append(.blobJr) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) NotificationCenter.default.post(name: willResignNotificationName, object: nil) testScheduler.advance() - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr]) } } @@ -128,15 +128,15 @@ final class FileStorageTests: XCTestCase { ) } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), nil) + try expectNoDifference(fileSystem.value.users(for: .fileURL), nil) users.append(.blob) users.append(.blobJr) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) NotificationCenter.default.post(name: willTerminateNotificationName, object: nil) testScheduler.advance() - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob, .blobJr]) } } @@ -149,12 +149,12 @@ final class FileStorageTests: XCTestCase { @Shared(.fileStorage(.anotherFileURL)) var otherUsers = [User]() users.append(.blob) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) - try XCTAssertNoDifference(fileSystem.value.users(for: .anotherFileURL), nil) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .anotherFileURL), nil) otherUsers.append(.blobJr) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) - try XCTAssertNoDifference(fileSystem.value.users(for: .anotherFileURL), [.blobJr]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .anotherFileURL), [.blobJr]) } } @@ -173,7 +173,7 @@ final class FileStorageTests: XCTestCase { .post(name: willResignNotificationName, object: nil) await Task.yield() - try XCTAssertNoDifference( + try expectNoDifference( JSONDecoder().decode([User].self, from: Data(contentsOf: .fileURL)), [.blob] ) @@ -191,7 +191,7 @@ final class FileStorageTests: XCTestCase { @Shared(.fileStorage(.fileURL)) var users = [User]() _ = users await Task.yield() - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) } } } @@ -207,7 +207,7 @@ final class FileStorageTests: XCTestCase { @Shared(.fileStorage(.fileURL)) var users = [User]() _ = users await Task.yield() - try XCTAssertNoDifference( + try expectNoDifference( JSONDecoder().decode([User].self, from: Data(contentsOf: .fileURL)), [.blob] ) @@ -226,11 +226,11 @@ final class FileStorageTests: XCTestCase { } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() await Task.yield() - XCTAssertNoDifference(users, [.blob]) + expectNoDifference(users, [.blob]) try JSONEncoder().encode([User.blobJr]).write(to: .fileURL) try await Task.sleep(nanoseconds: 10_000_000) - XCTAssertNoDifference(users, [.blobJr]) + expectNoDifference(users, [.blobJr]) } } } @@ -250,12 +250,12 @@ final class FileStorageTests: XCTestCase { @Shared(.fileStorage(.fileURL)) var users = [User]() users.append(.blob) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) + try expectNoDifference(fileSystem.value.users(for: .fileURL), [.blob]) try fileStorage.save(Data(), .fileURL) scheduler.run() - XCTAssertNoDifference(users, []) - try XCTAssertNoDifference(fileSystem.value.users(for: .fileURL), nil) + expectNoDifference(users, []) + try expectNoDifference(fileSystem.value.users(for: .fileURL), nil) } } @@ -270,11 +270,11 @@ final class FileStorageTests: XCTestCase { } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() await Task.yield() - XCTAssertNoDifference(users, [.blob]) + expectNoDifference(users, [.blob]) try FileManager.default.removeItem(at: .fileURL) try await Task.sleep(nanoseconds: 1_000_000) - XCTAssertNoDifference(users, []) + expectNoDifference(users, []) } } } @@ -291,16 +291,16 @@ final class FileStorageTests: XCTestCase { } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() await Task.yield() - XCTAssertNoDifference(users, [.blob]) + expectNoDifference(users, [.blob]) try FileManager.default.moveItem(at: .fileURL, to: .anotherFileURL) try await Task.sleep(nanoseconds: 1_000_000) - XCTAssertNoDifference(users, []) + expectNoDifference(users, []) try FileManager.default.removeItem(at: .fileURL) try FileManager.default.moveItem(at: .anotherFileURL, to: .fileURL) try await Task.sleep(nanoseconds: 1_000_000) - XCTAssertNoDifference(users, [.blob]) + expectNoDifference(users, [.blob]) } } } @@ -316,15 +316,15 @@ final class FileStorageTests: XCTestCase { } operation: { @Shared(.fileStorage(.fileURL)) var users = [User]() await Task.yield() - XCTAssertNoDifference(users, [.blob]) + expectNoDifference(users, [.blob]) try FileManager.default.removeItem(at: .fileURL) try await Task.sleep(nanoseconds: 1_000_000) - XCTAssertNoDifference(users, []) + expectNoDifference(users, []) try JSONEncoder().encode([User.blobJr]).write(to: .fileURL) try await Task.sleep(nanoseconds: 1_000_000) - XCTAssertNoDifference(users, [.blobJr]) + expectNoDifference(users, [.blobJr]) } } } @@ -413,13 +413,13 @@ final class FileStorageTests: XCTestCase { } operation: { @Shared(.fileStorage(.fileURL)) var users = [User.blob] await Task.yield() - XCTAssertNoDifference(users, [.blob]) + expectNoDifference(users, [.blob]) $users.withLock { $0 = [.blobJr] } // NB: Saved immediately $users.withLock { $0 = [.blobSr] } // NB: Throttled for 1 second try FileManager.default.removeItem(at: .fileURL) try await Task.sleep(nanoseconds: 1_200_000_000) - XCTAssertNoDifference(users, [.blob]) + expectNoDifference(users, [.blob]) try XCTAssertEqual(Data(contentsOf: .fileURL), Data()) } } diff --git a/Tests/ComposableArchitectureTests/Reducers/ForEachReducerTests.swift b/Tests/ComposableArchitectureTests/Reducers/ForEachReducerTests.swift index 2f0fb1efcc72..78643d05e074 100644 --- a/Tests/ComposableArchitectureTests/Reducers/ForEachReducerTests.swift +++ b/Tests/ComposableArchitectureTests/Reducers/ForEachReducerTests.swift @@ -70,6 +70,7 @@ final class ForEachReducerTests: BaseTCATestCase { await store.send(\.rows[id:1], "Blob Esq.") } + @available(*, deprecated, message: "TODO: Update to use case pathable syntax with Swift 5.9") @MainActor func testAutomaticEffectCancellation() async { if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) { diff --git a/Tests/ComposableArchitectureTests/Reducers/StackReducerTests.swift b/Tests/ComposableArchitectureTests/Reducers/StackReducerTests.swift index a72a0238e1af..ac5f9035f2b0 100644 --- a/Tests/ComposableArchitectureTests/Reducers/StackReducerTests.swift +++ b/Tests/ComposableArchitectureTests/Reducers/StackReducerTests.swift @@ -1,6 +1,7 @@ @_spi(Internals) import ComposableArchitecture import XCTest +@available(*, deprecated, message: "TODO: Update to use case pathable syntax with Swift 5.9") final class StackReducerTests: BaseTCATestCase { @MainActor func testStackStateSubscriptCase() { diff --git a/Tests/ComposableArchitectureTests/SharedTests.swift b/Tests/ComposableArchitectureTests/SharedTests.swift index 75dd38cfb159..b08347e084ed 100644 --- a/Tests/ComposableArchitectureTests/SharedTests.swift +++ b/Tests/ComposableArchitectureTests/SharedTests.swift @@ -961,9 +961,9 @@ final class SharedTests: XCTestCase { first.wrappedValue.name = "Blob" second.wrappedValue.name = "Blob Jr" - XCTAssertNoDifference(first.wrappedValue, User(id: 1, name: "Blob")) - XCTAssertNoDifference(second.wrappedValue, User(id: 2, name: "Blob Jr")) - XCTAssertNoDifference( + expectNoDifference(first.wrappedValue, User(id: 1, name: "Blob")) + expectNoDifference(second.wrappedValue, User(id: 2, name: "Blob Jr")) + expectNoDifference( sharedCollection.wrappedValue, [ User(id: 1, name: "Blob"), @@ -972,9 +972,9 @@ final class SharedTests: XCTestCase { ) sharedCollection.wrappedValue.swapAt(0, 1) - XCTAssertNoDifference(first.wrappedValue, User(id: 1, name: "Blob")) - XCTAssertNoDifference(second.wrappedValue, User(id: 2, name: "Blob Jr")) - XCTAssertNoDifference( + expectNoDifference(first.wrappedValue, User(id: 1, name: "Blob")) + expectNoDifference(second.wrappedValue, User(id: 2, name: "Blob Jr")) + expectNoDifference( sharedCollection.wrappedValue, [ User(id: 2, name: "Blob Jr"), @@ -984,9 +984,9 @@ final class SharedTests: XCTestCase { first.wrappedValue.name += ", M.D." second.wrappedValue.name += ", Esq." - XCTAssertNoDifference(first.wrappedValue, User(id: 1, name: "Blob, M.D.")) - XCTAssertNoDifference(second.wrappedValue, User(id: 2, name: "Blob Jr, Esq.")) - XCTAssertNoDifference( + expectNoDifference(first.wrappedValue, User(id: 1, name: "Blob, M.D.")) + expectNoDifference(second.wrappedValue, User(id: 2, name: "Blob Jr, Esq.")) + expectNoDifference( sharedCollection.wrappedValue, [ User(id: 2, name: "Blob Jr, Esq."), diff --git a/Tests/ComposableArchitectureTests/TaskResultTests.swift b/Tests/ComposableArchitectureTests/TaskResultTests.swift index 1c382025a4e2..bd091720e8c0 100644 --- a/Tests/ComposableArchitectureTests/TaskResultTests.swift +++ b/Tests/ComposableArchitectureTests/TaskResultTests.swift @@ -35,13 +35,13 @@ final class TaskResultTests: BaseTCATestCase { } XCTExpectFailure { - XCTAssertNoDifference( + expectNoDifference( TaskResult.failure(Failure1(message: "Something went wrong")), TaskResult.failure(Failure2(message: "Something went wrong")) ) } issueMatcher: { $0.compactDescription == """ - failed - XCTAssertNoDifference failed: … + failed - Difference: …   TaskResult.failure( − TaskResultTests.Failure1(message: "Something went wrong") diff --git a/Tests/ComposableArchitectureTests/TestStoreNonExhaustiveTests.swift b/Tests/ComposableArchitectureTests/TestStoreNonExhaustiveTests.swift index 3860bda2e360..32f4e176409a 100644 --- a/Tests/ComposableArchitectureTests/TestStoreNonExhaustiveTests.swift +++ b/Tests/ComposableArchitectureTests/TestStoreNonExhaustiveTests.swift @@ -658,6 +658,7 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase { await store.receive(\.response2) } + @available(*, deprecated) @MainActor func testXCTModifyExhaustive() async { struct State: Equatable { @@ -700,6 +701,7 @@ final class TestStoreNonExhaustiveTests: BaseTCATestCase { } } + @available(*, deprecated) @MainActor func testXCTModifyNonExhaustive() async { enum Action { case tap, response }