From 4481ab4ccc31547babfd45d394abae1c2fbc093e Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Thu, 18 Jun 2026 09:59:17 -0500 Subject: [PATCH 01/26] Add '.taskLocal' test trait. --- .../testing/NNNN-task-local-test-trait.md | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 proposals/testing/NNNN-task-local-test-trait.md diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md new file mode 100644 index 0000000000..4aba8f0fdd --- /dev/null +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -0,0 +1,166 @@ +# TaskLocal test trait + +* Proposal: [ST-NNNN](NNNN-task-local-test-trait.md) +* Authors: [Brandon Williams](https://github.com/mbrandonw), [Stephen Celis](https://github.com/stephencelis) +* Review Manager: TBD +* Status: **Awaiting review** +* Implementation: [swiftlang/swift-testing#NNNNN](https://github.com/swiftlang/swift-testing/compare/main...pointfreeco:swift-testing:task-local-test-trait) +* Review: ([pitch](https://forums.swift.org/...)) + +# Introduction + +A `.taskLocal` test trait is added to Testing that allows overriding a task local for a suite +or test. + +# Motivation + +In [ST-0007], test scoping traits were introduced to allow wrapping the execution +of tests so that code could be run before and after each test. The main +motivation for this feature is overriding `[TaskLocal]`s for tests, and the +proposal mentions a [future direction][task-local-future-direction] to add a +convenience trait specifically for overriding task locals. This proposal adds that +convenience. + +[TaskLocals]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0311-task-locals.md +[ST-0007]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md +[task-local-future-direction]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md#convenience-trait-for-setting-task-locals + +There are two primary motivations for adding this convenience: + +* Task locals are by far the [most common] reason to define custom `TestScoping` + conformances. However, it takes quite a bit of repetitive work to define such + conformances. For example, a trait to override a boolean task local looks like this: + + [most common]: https://github.com/search?q=testscoping+language%3ASwift+&type=code + + ```swift + struct _IsEnabledTrait: SuiteTrait, TestScoping, TestTrait { + let isEnabled: Bool + let isRecursive = true + func provideScope( + for test: Test, + testCase: Test.Case?, + performing function: () async throws -> Void + ) async throws { + try await FeatureFlags.$isEnabled.withValue(isEnabled) { + try await function() + } + } + } + extension Trait where Self == _IsEnabledTrait { + static func isEnabled(_ isEnabled: Bool) -> Self { + Self(isEnabled: isEnabled) + } + } + ``` + + Ideally, we can drastically streamline overriding task locals in test with a dedicate convenience + trait. + +* When a task local is defined in a reusable library, the library maintainer cannot easily define a + test trait for overriding the task local. Non-test targets cannot access Testing symbols, and so + such a helper needs to be defined in a dedicated "test support" library. This introduces more + inconvenience when shipping task locals in libraries. A dedicate `.taskLocal` trait allows one + to override task locals in tests without creating a dedicate test support library. + +# Proposed solution + +A `.taskLocal` trait will be defined in Testing that allows overriding a task local for a single +test or a whole suite: + +```swift +@Suite(.taskLocal(FeatureFlags.$isEnabled, true)) +struct MySuite { + // ... +} +``` + +# Detailed design + +A new type will be defined to conform to `TestScoping`, as well as a static function for ease of +constructing the trait: + +```swift +extension Trait { + /// Constructs a trait that overrides a task local value for the duration of a test + /// or suite. + /// + /// ```swift + /// @Suite(.taskLocal($myValue, 42)) + /// struct MyTests { + /// // ... + /// } + /// ``` + /// + /// - Note: The task local must be defined outside the test target where the trait is used. + /// + /// - Parameters: + /// - taskLocal: The task local to override. + /// - value: The value to set. + public static func taskLocal( + _ taskLocal: TaskLocal, + _ value: Value + ) -> Self + where Self == TaskLocalTrait { + TaskLocalTrait(taskLocal: taskLocal, value: value) + } +} + +/// A type that that overrides a task local value for the scope of a test. +/// +/// To add this trait to a test, use ``Trait/taskLocal(_:_:)``. +public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrait { + public let isRecursive = true + + fileprivate let taskLocal: TaskLocal + fileprivate let value: Value + + public func provideScope( + for test: Test, + testCase: Test.Case?, + performing function: @concurrent () async throws -> Void + ) async throws { + try await taskLocal.withValue(value, operation: function) + } +} +``` + +An important caveat to note (and is detailed in the documentation) is that the task local being +overridden _must_ be defined outside the test target. One cannot override a task local like so: + +```swift +@TaskLocal var isEnabled = false + +@Test(.taskLocal($isEnabled, true)) // 🛑 Cannot find '$isEnabled' in scope +func test() { + // ... +} +``` + +This is because currently the `@Test` macro cannot see the `$isEnabled` symbol created by the +`@TaskLocal` macro. There are use cases for defining task locals in the test target along side tests +that want to override them, but it's not common, and you always have the option to move the task +local to a separate target. + +## Source compatibility + +The proposed APIs are purely additive. + +## Alternatives considered + +A more general name for this API could be used instead of `.taskLocal`: + +```swift +@Test(.set($isEnabled, true)) +func test() { + // ... +} +``` + +Also we could decide to not add the trait since technically it is possible to implement its +functionality manually for each task local. + +## Future directions + +If Swift macros gain the ability to see symbols defined by other macros, we will be able to drop +the restriction that task locals be defined outside the test target. From d9a994cbcee66a7490fe4e59e0e4f21fe06c45fc Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 09:02:21 -0700 Subject: [PATCH 02/26] Fix typos and enhance documentation for task local trait Corrected typos and improved clarity in the documentation regarding the task local test trait. --- proposals/testing/NNNN-task-local-test-trait.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 4aba8f0fdd..c7bf479875 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -54,14 +54,14 @@ There are two primary motivations for adding this convenience: } ``` - Ideally, we can drastically streamline overriding task locals in test with a dedicate convenience + Ideally, we can drastically streamline overriding task locals in test with a dedicated convenience trait. * When a task local is defined in a reusable library, the library maintainer cannot easily define a test trait for overriding the task local. Non-test targets cannot access Testing symbols, and so such a helper needs to be defined in a dedicated "test support" library. This introduces more - inconvenience when shipping task locals in libraries. A dedicate `.taskLocal` trait allows one - to override task locals in tests without creating a dedicate test support library. + inconvenience when shipping task locals in libraries. A dedicated `.taskLocal` trait allows one + to override task locals in tests without creating a dedicated test support library. # Proposed solution @@ -110,7 +110,7 @@ extension Trait { /// /// To add this trait to a test, use ``Trait/taskLocal(_:_:)``. public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrait { - public let isRecursive = true + public var isRecursive: Bool { true } fileprivate let taskLocal: TaskLocal fileprivate let value: Value @@ -153,7 +153,7 @@ A more general name for this API could be used instead of `.taskLocal`: ```swift @Test(.set($isEnabled, true)) func test() { - // ... + // ... } ``` From 1e023f2e503f0409636dd19ad9cbd4ffda9a0874 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 17:26:54 -0700 Subject: [PATCH 03/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Jonathan Grynspan --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index c7bf479875..0404eb21a7 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -34,7 +34,7 @@ There are two primary motivations for adding this convenience: [most common]: https://github.com/search?q=testscoping+language%3ASwift+&type=code ```swift - struct _IsEnabledTrait: SuiteTrait, TestScoping, TestTrait { + struct IsEnabledTrait: SuiteTrait, TestScoping, TestTrait { let isEnabled: Bool let isRecursive = true func provideScope( From f7ed72d7a389c049bd1c6fcfc8fab4e12b0e37ff Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 17:27:01 -0700 Subject: [PATCH 04/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Jonathan Grynspan --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 0404eb21a7..dedd2d43a9 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -47,7 +47,7 @@ There are two primary motivations for adding this convenience: } } } - extension Trait where Self == _IsEnabledTrait { + extension Trait where Self == IsEnabledTrait { static func isEnabled(_ isEnabled: Bool) -> Self { Self(isEnabled: isEnabled) } From 087d945e0e85376af6ec5724b57181fad3859340 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 17:33:10 -0700 Subject: [PATCH 05/26] Feedback --- .../testing/NNNN-task-local-test-trait.md | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index dedd2d43a9..75baaac5d2 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -85,6 +85,10 @@ extension Trait { /// Constructs a trait that overrides a task local value for the duration of a test /// or suite. /// + /// - Parameters: + /// - taskLocal: The task local to override. + /// - value: The value to set. + /// /// ```swift /// @Suite(.taskLocal($myValue, 42)) /// struct MyTests { @@ -93,10 +97,6 @@ extension Trait { /// ``` /// /// - Note: The task local must be defined outside the test target where the trait is used. - /// - /// - Parameters: - /// - taskLocal: The task local to override. - /// - value: The value to set. public static func taskLocal( _ taskLocal: TaskLocal, _ value: Value @@ -148,16 +148,13 @@ The proposed APIs are purely additive. ## Alternatives considered -A more general name for this API could be used instead of `.taskLocal`: +A few other spellings have been proposed: -```swift -@Test(.set($isEnabled, true)) -func test() { - // ... -} -``` +* `.set($isEnabled, true)` +* `.withValue(true, for: $isEnabled)` +* `.binding($isEnabled, to: true)` -Also we could decide to not add the trait since technically it is possible to implement its +We could also decide to not add the trait since technically it is possible to implement its functionality manually for each task local. ## Future directions From e1e7c8d4c7b5d26835dcb743ba4c4aa28fd6d9bd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:05:33 -0700 Subject: [PATCH 06/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Konrad `ktoso` Malawski --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 75baaac5d2..8531a8ae31 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -86,7 +86,7 @@ extension Trait { /// or suite. /// /// - Parameters: - /// - taskLocal: The task local to override. + /// - taskLocal: The task local to bind the value to. /// - value: The value to set. /// /// ```swift From d11e8e188e3bbde2fdce4644eff970f4c7d49ef3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:05:45 -0700 Subject: [PATCH 07/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Konrad `ktoso` Malawski --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 8531a8ae31..8e4f5df6bb 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -82,7 +82,7 @@ constructing the trait: ```swift extension Trait { - /// Constructs a trait that overrides a task local value for the duration of a test + /// Constructs a trait that binds a task local value for the duration of a test /// or suite. /// /// - Parameters: From f74902423df5eee9e9b7d39a7e587640c024a5d8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:05:54 -0700 Subject: [PATCH 08/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Konrad `ktoso` Malawski --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 8e4f5df6bb..a58452e40b 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -126,7 +126,7 @@ public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrai ``` An important caveat to note (and is detailed in the documentation) is that the task local being -overridden _must_ be defined outside the test target. One cannot override a task local like so: +bound _must_ be defined outside the test target. One cannot override a task local like so: ```swift @TaskLocal var isEnabled = false From 612a21b766c45ff8c287db880af5875f814cd8b6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:06:31 -0700 Subject: [PATCH 09/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Konrad `ktoso` Malawski --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index a58452e40b..d173660015 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -21,7 +21,7 @@ proposal mentions a [future direction][task-local-future-direction] to add a convenience trait specifically for overriding task locals. This proposal adds that convenience. -[TaskLocals]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0311-task-locals.md +[SE-0331: TaskLocal]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0311-task-locals.md [ST-0007]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md [task-local-future-direction]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md#convenience-trait-for-setting-task-locals From 8313095f6f66ec99c9461ae2ec14f82e9d921683 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:06:50 -0700 Subject: [PATCH 10/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Konrad `ktoso` Malawski --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index d173660015..193fbcc446 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -16,7 +16,7 @@ or test. In [ST-0007], test scoping traits were introduced to allow wrapping the execution of tests so that code could be run before and after each test. The main -motivation for this feature is overriding `[TaskLocal]`s for tests, and the +motivation for this feature is overriding [`TaskLocal`s][SE-0311: TaskLocal] for tests, and the proposal mentions a [future direction][task-local-future-direction] to add a convenience trait specifically for overriding task locals. This proposal adds that convenience. From e2ae5ad868b9ffb8b5a2922bfde4a3f3d3cba550 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:07:00 -0700 Subject: [PATCH 11/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Konrad `ktoso` Malawski --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 193fbcc446..0dba61ab01 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -97,7 +97,7 @@ extension Trait { /// ``` /// /// - Note: The task local must be defined outside the test target where the trait is used. - public static func taskLocal( + public static func taskLocal( _ taskLocal: TaskLocal, _ value: Value ) -> Self From 9cf7893c26adab4463d0e5f2672b448bb023cf63 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:09:24 -0700 Subject: [PATCH 12/26] Clarify task local trait terminology in documentation Updated terminology from 'overriding' to 'binding' for clarity in the context of task locals. Adjusted related descriptions to reflect this change throughout the document. --- proposals/testing/NNNN-task-local-test-trait.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 0dba61ab01..31fe9d2629 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -9,7 +9,7 @@ # Introduction -A `.taskLocal` test trait is added to Testing that allows overriding a task local for a suite +A `.taskLocal` test trait is added to Testing that allows binding a task local for a suite or test. # Motivation @@ -18,7 +18,7 @@ In [ST-0007], test scoping traits were introduced to allow wrapping the executio of tests so that code could be run before and after each test. The main motivation for this feature is overriding [`TaskLocal`s][SE-0311: TaskLocal] for tests, and the proposal mentions a [future direction][task-local-future-direction] to add a -convenience trait specifically for overriding task locals. This proposal adds that +convenience trait specifically for binding task locals. This proposal adds that convenience. [SE-0331: TaskLocal]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0311-task-locals.md @@ -29,7 +29,7 @@ There are two primary motivations for adding this convenience: * Task locals are by far the [most common] reason to define custom `TestScoping` conformances. However, it takes quite a bit of repetitive work to define such - conformances. For example, a trait to override a boolean task local looks like this: + conformances. For example, a trait to bind a boolean task local looks like this: [most common]: https://github.com/search?q=testscoping+language%3ASwift+&type=code @@ -61,7 +61,7 @@ There are two primary motivations for adding this convenience: test trait for overriding the task local. Non-test targets cannot access Testing symbols, and so such a helper needs to be defined in a dedicated "test support" library. This introduces more inconvenience when shipping task locals in libraries. A dedicated `.taskLocal` trait allows one - to override task locals in tests without creating a dedicated test support library. + to bind task locals in tests without creating a dedicated test support library. # Proposed solution @@ -106,7 +106,7 @@ extension Trait { } } -/// A type that that overrides a task local value for the scope of a test. +/// A type that that binds a task local value for the scope of a test. /// /// To add this trait to a test, use ``Trait/taskLocal(_:_:)``. public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrait { @@ -126,7 +126,7 @@ public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrai ``` An important caveat to note (and is detailed in the documentation) is that the task local being -bound _must_ be defined outside the test target. One cannot override a task local like so: +bound _must_ be defined outside the test target. One cannot bind a task local like so: ```swift @TaskLocal var isEnabled = false From 2c20426087c25540e3d4ee77ead7ef1c706b49f8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:11:17 -0700 Subject: [PATCH 13/26] Fixes --- proposals/testing/NNNN-task-local-test-trait.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 31fe9d2629..821eed8200 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -29,7 +29,7 @@ There are two primary motivations for adding this convenience: * Task locals are by far the [most common] reason to define custom `TestScoping` conformances. However, it takes quite a bit of repetitive work to define such - conformances. For example, a trait to bind a boolean task local looks like this: + conformances. For example, a trait to bind a boolean to a task local looks like this: [most common]: https://github.com/search?q=testscoping+language%3ASwift+&type=code @@ -106,7 +106,7 @@ extension Trait { } } -/// A type that that binds a task local value for the scope of a test. +/// A type that binds a task local value for the scope of a test. /// /// To add this trait to a test, use ``Trait/taskLocal(_:_:)``. public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrait { From 30b37ed07d94108781edf110a934188011a8cc6e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 18 Jun 2026 23:15:37 -0700 Subject: [PATCH 14/26] Update NNNN-task-local-test-trait.md --- proposals/testing/NNNN-task-local-test-trait.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 821eed8200..1b52caac8a 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -9,7 +9,7 @@ # Introduction -A `.taskLocal` test trait is added to Testing that allows binding a task local for a suite +A `.taskLocal` test trait is added to Testing that allows binding a value to a task local for a suite or test. # Motivation @@ -61,7 +61,7 @@ There are two primary motivations for adding this convenience: test trait for overriding the task local. Non-test targets cannot access Testing symbols, and so such a helper needs to be defined in a dedicated "test support" library. This introduces more inconvenience when shipping task locals in libraries. A dedicated `.taskLocal` trait allows one - to bind task locals in tests without creating a dedicated test support library. + to bind values to task locals in tests without creating a dedicated test support library. # Proposed solution @@ -126,7 +126,7 @@ public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrai ``` An important caveat to note (and is detailed in the documentation) is that the task local being -bound _must_ be defined outside the test target. One cannot bind a task local like so: +bound _must_ be defined outside the test target. One cannot bind a task local value like so: ```swift @TaskLocal var isEnabled = false From fe9ba30a61dc4422429696c9e4eff1e457fd8de7 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 19 Jun 2026 14:04:05 -0700 Subject: [PATCH 15/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Stuart Montgomery --- proposals/testing/NNNN-task-local-test-trait.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 1b52caac8a..4e45fa31f9 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -112,8 +112,6 @@ extension Trait { public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrait { public var isRecursive: Bool { true } - fileprivate let taskLocal: TaskLocal - fileprivate let value: Value public func provideScope( for test: Test, From aa3ae4cf43bbffde58128cfd488195ba133c51dd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 19 Jun 2026 14:04:21 -0700 Subject: [PATCH 16/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Stuart Montgomery --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 4e45fa31f9..94bc7a0628 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -4,7 +4,7 @@ * Authors: [Brandon Williams](https://github.com/mbrandonw), [Stephen Celis](https://github.com/stephencelis) * Review Manager: TBD * Status: **Awaiting review** -* Implementation: [swiftlang/swift-testing#NNNNN](https://github.com/swiftlang/swift-testing/compare/main...pointfreeco:swift-testing:task-local-test-trait) +* Implementation: [swiftlang/swift-testing#1764](https://github.com/swiftlang/swift-testing/pull/1764) * Review: ([pitch](https://forums.swift.org/...)) # Introduction From 883510e69a5d816fd946e7502434270fa55850cd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 19 Jun 2026 14:05:14 -0700 Subject: [PATCH 17/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Stuart Montgomery --- proposals/testing/NNNN-task-local-test-trait.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 94bc7a0628..c1831f514a 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -117,9 +117,7 @@ public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrai for test: Test, testCase: Test.Case?, performing function: @concurrent () async throws -> Void - ) async throws { - try await taskLocal.withValue(value, operation: function) - } + ) async throws } ``` From 7c08931a39df466d6f0b03a1444b7a8f2eb3dfa5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 19 Jun 2026 14:05:34 -0700 Subject: [PATCH 18/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Stuart Montgomery --- proposals/testing/NNNN-task-local-test-trait.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index c1831f514a..9838827803 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -108,6 +108,9 @@ extension Trait { /// A type that binds a task local value for the scope of a test. /// +/// When an instance of this trait is applied to a suite, it is recursively +/// inherited by all child suites and tests. +/// /// To add this trait to a test, use ``Trait/taskLocal(_:_:)``. public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrait { public var isRecursive: Bool { true } From cf1ba5e267706940a11e03f714f611530f20dcd5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 19 Jun 2026 14:05:52 -0700 Subject: [PATCH 19/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Stuart Montgomery --- proposals/testing/NNNN-task-local-test-trait.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 9838827803..5cf6c25cf4 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -101,9 +101,7 @@ extension Trait { _ taskLocal: TaskLocal, _ value: Value ) -> Self - where Self == TaskLocalTrait { - TaskLocalTrait(taskLocal: taskLocal, value: value) - } + where Self == TaskLocalTrait } /// A type that binds a task local value for the scope of a test. From 92781aab3c61d5fc2244e5d348c450b07834c59c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 19 Jun 2026 14:06:37 -0700 Subject: [PATCH 20/26] Update proposals/testing/NNNN-task-local-test-trait.md Co-authored-by: Stuart Montgomery --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 5cf6c25cf4..6c080fa71e 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -96,7 +96,7 @@ extension Trait { /// } /// ``` /// - /// - Note: The task local must be defined outside the test target where the trait is used. + /// - Note: The task local must be declared in a separate module than the module where the trait is used. public static func taskLocal( _ taskLocal: TaskLocal, _ value: Value From 7e4f0b6e2d5fe2d261b35ca0db9cfcb3741819e3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 19 Jun 2026 14:10:49 -0700 Subject: [PATCH 21/26] Update NNNN-task-local-test-trait.md --- proposals/testing/NNNN-task-local-test-trait.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 6c080fa71e..68589eeece 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -21,7 +21,7 @@ proposal mentions a [future direction][task-local-future-direction] to add a convenience trait specifically for binding task locals. This proposal adds that convenience. -[SE-0331: TaskLocal]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0311-task-locals.md +[SE-0311: TaskLocal]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0311-task-locals.md [ST-0007]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md [task-local-future-direction]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md#convenience-trait-for-setting-task-locals @@ -34,7 +34,7 @@ There are two primary motivations for adding this convenience: [most common]: https://github.com/search?q=testscoping+language%3ASwift+&type=code ```swift - struct IsEnabledTrait: SuiteTrait, TestScoping, TestTrait { + struct IsEnabledTrait: SuiteTrait, TestTrait, TestScoping { let isEnabled: Bool let isRecursive = true func provideScope( @@ -110,10 +110,9 @@ extension Trait { /// inherited by all child suites and tests. /// /// To add this trait to a test, use ``Trait/taskLocal(_:_:)``. -public struct TaskLocalTrait: SuiteTrait, TestScoping, TestTrait { +public struct TaskLocalTrait: SuiteTrait, TestTrait, TestScoping { public var isRecursive: Bool { true } - public func provideScope( for test: Test, testCase: Test.Case?, @@ -156,5 +155,7 @@ functionality manually for each task local. ## Future directions -If Swift macros gain the ability to see symbols defined by other macros, we will be able to drop -the restriction that task locals be defined outside the test target. +If this macro limitation were resolved, then this proposed API would become usable in more contexts/situations + +If Swift macros gain the ability to see symbols defined by other macros, this proposed API would +become usable in more contexts/situations. From b0dff586bae695374492d2877a3c3947cd0a6f2a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 19 Jun 2026 14:11:39 -0700 Subject: [PATCH 22/26] Fix TaskLocal references and link formatting Corrected references to TaskLocal in the motivation section and updated the link formatting. --- proposals/testing/NNNN-task-local-test-trait.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 68589eeece..4083f92a7f 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -16,12 +16,12 @@ or test. In [ST-0007], test scoping traits were introduced to allow wrapping the execution of tests so that code could be run before and after each test. The main -motivation for this feature is overriding [`TaskLocal`s][SE-0311: TaskLocal] for tests, and the +motivation for this feature is overriding [`TaskLocal`s][SE-0311] for tests, and the proposal mentions a [future direction][task-local-future-direction] to add a convenience trait specifically for binding task locals. This proposal adds that convenience. -[SE-0311: TaskLocal]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0311-task-locals.md +[SE-0311]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0311-task-locals.md [ST-0007]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md [task-local-future-direction]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md#convenience-trait-for-setting-task-locals From aca6e456bac953f66d53ce34eb10a039e0b04b92 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 22 Jun 2026 12:18:39 -0700 Subject: [PATCH 23/26] Update NNNN-task-local-test-trait.md --- proposals/testing/NNNN-task-local-test-trait.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 4083f92a7f..1619375e47 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -96,7 +96,7 @@ extension Trait { /// } /// ``` /// - /// - Note: The task local must be declared in a separate module than the module where the trait is used. + /// - Note: You must define the task local outside the test target where the trait is used. public static func taskLocal( _ taskLocal: TaskLocal, _ value: Value @@ -106,13 +106,8 @@ extension Trait { /// A type that binds a task local value for the scope of a test. /// -/// When an instance of this trait is applied to a suite, it is recursively -/// inherited by all child suites and tests. -/// /// To add this trait to a test, use ``Trait/taskLocal(_:_:)``. public struct TaskLocalTrait: SuiteTrait, TestTrait, TestScoping { - public var isRecursive: Bool { true } - public func provideScope( for test: Test, testCase: Test.Case?, From 54fe282cbe53eafb471de8ca136376ca1518a214 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 22 Jun 2026 12:57:58 -0700 Subject: [PATCH 24/26] Update NNNN-task-local-test-trait.md --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 1619375e47..35cd7ae3c5 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -5,7 +5,7 @@ * Review Manager: TBD * Status: **Awaiting review** * Implementation: [swiftlang/swift-testing#1764](https://github.com/swiftlang/swift-testing/pull/1764) -* Review: ([pitch](https://forums.swift.org/...)) +* Review: ([pitch](https://forums.swift.org/t/pitch-add-a-tasklocal-trait-to-swift-testing/87603)) # Introduction From 5901b56130ebd91e674f5cbeacd71687549e6f57 Mon Sep 17 00:00:00 2001 From: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:50:04 -0500 Subject: [PATCH 25/26] Update NNNN-task-local-test-trait.md --- proposals/testing/NNNN-task-local-test-trait.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index 35cd7ae3c5..d8af57cd09 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -150,7 +150,5 @@ functionality manually for each task local. ## Future directions -If this macro limitation were resolved, then this proposed API would become usable in more contexts/situations - If Swift macros gain the ability to see symbols defined by other macros, this proposed API would become usable in more contexts/situations. From 02bd8f556ed9a0d6595d2f8810d374a1c867cecb Mon Sep 17 00:00:00 2001 From: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Date: Thu, 25 Jun 2026 12:42:05 -0500 Subject: [PATCH 26/26] Update NNNN-task-local-test-trait.md --- proposals/testing/NNNN-task-local-test-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/testing/NNNN-task-local-test-trait.md b/proposals/testing/NNNN-task-local-test-trait.md index d8af57cd09..c830cd1608 100644 --- a/proposals/testing/NNNN-task-local-test-trait.md +++ b/proposals/testing/NNNN-task-local-test-trait.md @@ -141,7 +141,7 @@ The proposed APIs are purely additive. A few other spellings have been proposed: -* `.set($isEnabled, true)` +* `$isEnabled.set(true)` * `.withValue(true, for: $isEnabled)` * `.binding($isEnabled, to: true)`