From ea17a92ded0ece6949b01595e7fd244a4b847d28 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 10 Oct 2025 17:47:36 +0900 Subject: [PATCH 1/2] [Concurrency] Adopt typed throws in Task creation APIs --- stdlib/public/Concurrency/Task+init.swift.gyb | 39 ++++++++++++------- .../sendable_metatype_typecheck.swift | 4 +- .../task_naming_availability.swift | 8 ++-- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/stdlib/public/Concurrency/Task+init.swift.gyb b/stdlib/public/Concurrency/Task+init.swift.gyb index 36a2e597e0be3..83dccc9d67891 100644 --- a/stdlib/public/Concurrency/Task+init.swift.gyb +++ b/stdlib/public/Concurrency/Task+init.swift.gyb @@ -26,7 +26,7 @@ import Swift % [ # PARAMS % 'name: String? = nil', % 'priority: TaskPriority? = nil', -% '@_inheritActorContext @_implicitSelfCapture operation: sending @escaping @isolated(any) () async throws -> Success', +% '@_inheritActorContext @_implicitSelfCapture operation: sending @escaping @isolated(any) () async throws(Failure) -> Success', % ]), % # ==== -------------------------------------------------------------------- % ([ # METHOD_VARIANT @@ -40,7 +40,7 @@ import Swift % [ # PARAMS % 'name: String? = nil', % 'priority: TaskPriority? = nil', -% 'operation: sending @escaping @isolated(any) () async throws -> Success', +% 'operation: sending @escaping @isolated(any) () async throws(Failure) -> Success', % ]), % # ==== ------------------------------------------------------------------------------------------------------------- % # ==== With task executor, but available only since 6.0 @@ -56,7 +56,7 @@ import Swift % 'name: String? = nil', % 'executorPreference taskExecutor: (any TaskExecutor)?', % 'priority: TaskPriority? = nil', -% 'operation: sending @escaping () async throws -> Success', +% 'operation: sending @escaping () async throws(Failure) -> Success', % ]), % # ==== -------------------------------------------------------------------- % ([ # METHOD_VARIANT @@ -71,7 +71,7 @@ import Swift % 'name: String? = nil', % 'executorPreference taskExecutor: (any TaskExecutor)?', % 'priority: TaskPriority? = nil', -% 'operation: sending @escaping () async throws -> Success', +% 'operation: sending @escaping () async throws(Failure) -> Success', % ]), % # !!!! ------------------------------------------------------------------------------------------------------------- % # !!!! Legacy / Source Compatibility "Shims" @@ -91,6 +91,7 @@ import Swift % ], % [ # PARAMS % 'priority: TaskPriority? = nil', +% # detach does not support typed throws, it's a legacy api for compat only % '@_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping @isolated(any) () async throws -> Success', % ]), % # ==== Legacy API: runDetached @@ -105,6 +106,7 @@ import Swift % ], % [ # PARAMS % 'priority: TaskPriority? = nil', +% # runDetached does not support typed throws, it's a legacy api for compat only % '@_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping @isolated(any) () async throws -> Success', % ]), % # ==== Legacy API: asyncDetached @@ -119,6 +121,7 @@ import Swift % ], % [ # PARAMS % 'priority: TaskPriority? = nil', +% # asyncDetached does not support typed throws, it's a legacy api for compat only % '@_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping @isolated(any) () async throws -> Success', % ]), % # ==== Legacy API: async @@ -133,6 +136,7 @@ import Swift % ], % [ # PARAMS % 'priority: TaskPriority? = nil', +% # async does not support typed throws, it's a legacy api for compat only % '@_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping @isolated(any) () async throws -> Success', % ]), % ]: @@ -149,8 +153,10 @@ import Swift % HAS_ISOLATED_ANY = any('@isolated(any)' in param for param in PARAMS) % IS_DEPRECATED = any('deprecated' in a for a in ALL_AVAILABILITY) % -% if IS_THROWING: -% FAILURE_TYPE = 'Error' +% if IS_THROWING and IS_TOP_LEVEL_FUNC: +% FAILURE_TYPE = 'Error' # the legacy top-level funcs don't have a generic Failure, so we keep the un-typed throw +% elif IS_THROWING: +% FAILURE_TYPE = 'Failure' # use typed throws, the thrown type is Failure of the Task<_, Failure> type % else: % FAILURE_TYPE = 'Never' % end @@ -162,6 +168,7 @@ def adjust_params_for_kind(params): for p in params: np = p if not IS_THROWING: + np = np.replace("throws(Failure)", "") np = np.replace("throws", "") res.append(np) return res @@ -184,8 +191,12 @@ else: % # ==================================================================================================================== % if not IS_TOP_LEVEL_FUNC: +% if IS_THROWING: +extension Task { // throwing Failure error type +% else: extension Task where Failure == ${FAILURE_TYPE} { % end +% end % # -------------------------------------------------------------------------------------------------------------------- #if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @@ -285,8 +296,10 @@ extension Task where Failure == ${FAILURE_TYPE} { /// - Returns: A reference to the task. % end # IS_DEPRECATED ${"\n ".join(adjust_availability(ALL_AVAILABILITY))} +% if not IS_THROWING: @discardableResult - public ${METHOD_VARIANT}( // Task ${METHOD_VARIANT} +% end + public ${METHOD_VARIANT}( ${",\n ".join(adjust_params_for_kind(PARAMS))} ) ${ARROW_RETURN_TYPE}{ @@ -323,7 +336,7 @@ extension Task where Failure == ${FAILURE_TYPE} { initialTaskExecutorConsuming: taskExecutor, % end taskName: nameBytes.baseAddress!._rawValue, - operation: operation).0 + operation: operation).0 // Task ${METHOD_VARIANT} } #else // no $BuiltinCreateAsyncTaskOwnedTaskExecutor // legacy branch for the non-consuming task executor @@ -338,7 +351,7 @@ extension Task where Failure == ${FAILURE_TYPE} { % end initialTaskExecutor: executorBuiltin, taskName: nameBytes.baseAddress!._rawValue, - operation: operation).0 + operation: operation).0 // Task ${METHOD_VARIANT} } #endif // $BuiltinCreateAsyncTaskOwnedTaskExecutor % else: # if no TASK_EXECUTOR @@ -350,7 +363,7 @@ extension Task where Failure == ${FAILURE_TYPE} { initialSerialExecutor: builtinSerialExecutor, % end taskName: nameBytes.baseAddress!._rawValue, - operation: operation).0 + operation: operation).0 // Task ${METHOD_VARIANT} } % end # if no HAS_TASK_EXECUTOR } // let name @@ -368,7 +381,7 @@ extension Task where Failure == ${FAILURE_TYPE} { initialSerialExecutor: builtinSerialExecutor, % end initialTaskExecutorConsuming: taskExecutor, - operation: operation).0 + operation: operation).0 // Task ${METHOD_VARIANT} #else // legacy branch for the non-consuming task executor let executorBuiltin: Builtin.Executor = @@ -380,7 +393,7 @@ extension Task where Failure == ${FAILURE_TYPE} { initialSerialExecutor: builtinSerialExecutor, % end initialTaskExecutor: executorBuiltin, - operation: operation).0 + operation: operation).0 // Task ${METHOD_VARIANT} #endif } % end # HAS_TASK_EXECUTOR @@ -392,7 +405,7 @@ extension Task where Failure == ${FAILURE_TYPE} { % if HAS_ISOLATED_ANY: initialSerialExecutor: builtinSerialExecutor, % end - operation: operation).0 + operation: operation).0 // Task ${METHOD_VARIANT} } % if IS_INIT: diff --git a/test/Concurrency/sendable_metatype_typecheck.swift b/test/Concurrency/sendable_metatype_typecheck.swift index 89d57582588fc..acc9708453a4a 100644 --- a/test/Concurrency/sendable_metatype_typecheck.swift +++ b/test/Concurrency/sendable_metatype_typecheck.swift @@ -190,7 +190,7 @@ func f(_: T.Type) { } func sendableSequence(_ s: S) throws { - Task.detached { + _ = Task.detached { for try await i in s { print(i) } @@ -198,7 +198,7 @@ func sendableSequence(_ s: S) throws { } func nonSendableSequence(_ s: S) throws { - Task.detached { + _ = Task.detached { for try await i in s { // expected-warning{{capture of non-Sendable type 'S.AsyncIterator.Type' in an isolated closure}} // expected-warning@-1{{capture of non-Sendable type 'S.Type' in an isolated closure}} print(i) diff --git a/test/Concurrency/task_naming_availability.swift b/test/Concurrency/task_naming_availability.swift index c7c45d67e8216..24194a595c40d 100644 --- a/test/Concurrency/task_naming_availability.swift +++ b/test/Concurrency/task_naming_availability.swift @@ -10,9 +10,9 @@ func testName() { @available(SwiftStdlib 6.0, *) func taskExecutor() async { Task(name: "name", executorPreference: nil) { } - Task(name: "name", executorPreference: nil) { throw Boom() } + _ = Task(name: "name", executorPreference: nil) { throw Boom() } - Task.detached(name: "name", executorPreference: nil) { throw Boom() } + _ = Task.detached(name: "name", executorPreference: nil) { throw Boom() } await withTaskGroup(of: Void.self) { group in group.addTask(name: "name", executorPreference: nil) { @@ -40,10 +40,10 @@ func taskExecutor() async { @available(SwiftStdlib 5.1, *) func backDeployedNames() async { Task(name: "name") { } - Task(name: "name") { throw Boom() } + _ = Task(name: "name") { throw Boom() } Task.detached(name: "name") { } - Task.detached(name: "name") { throw Boom() } + _ = Task.detached(name: "name") { throw Boom() } await withTaskGroup(of: Void.self) { group in group.addTask(name: "name") { From 4709e23f040c45ec0525f7347a82302358c2dc49 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 10 Oct 2025 18:48:04 +0900 Subject: [PATCH 2/2] adopt typed throw in Task.value --- stdlib/public/Concurrency/Task.swift | 10 +++++++--- test/Concurrency/Runtime/task_creation.swift | 11 ++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 3d2b9caaf572a..884496e5d4cb2 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -167,8 +167,12 @@ extension Task { /// /// - Returns: The task's result. public var value: Success { - get async throws { - return try await _taskFutureGetThrowing(_task) + get async throws(Failure) { + do { + return try await _taskFutureGetThrowing(_task) + } catch { + throw (error as! Failure) // as!-safe, because typed throw on the operation closure + } } } @@ -189,7 +193,7 @@ extension Task { do { return .success(try await value) } catch { - return .failure(error as! Failure) // as!-safe, guaranteed to be Failure + return .failure(error) // as!-safe, guaranteed to be Failure } } } diff --git a/test/Concurrency/Runtime/task_creation.swift b/test/Concurrency/Runtime/task_creation.swift index b834bc61719cd..f2b57fc634b88 100644 --- a/test/Concurrency/Runtime/task_creation.swift +++ b/test/Concurrency/Runtime/task_creation.swift @@ -31,6 +31,15 @@ enum SomeError: Error { return 7 } + let t2f = Task { () throws(SomeError) -> Int in + throw SomeError.bad + } + do { + try await t2f.value + } catch { + let err: SomeError = error // confirm it was a typed throw + } + let t3 = Task.detached { return 9 } @@ -43,7 +52,7 @@ enum SomeError: Error { return 11 } - let result = try! await t1.get() + t2.get() + t3.get() + t4.get() + let result = try! await t1.value + t2.value + t3.value + t4.value assert(result == 32) } }