From 9608c1d478858d8555699c029082bfa3dabc88c4 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 25 Feb 2026 12:27:33 -0500 Subject: [PATCH 1/2] Simplify the expansion of `@Test` by eliminating the `isolated` thunk. This PR removes one of the thunks we emit when expanding `@Test`. For example, given the following test function: ```swift @Test func foo() {} ``` We currently emit, approximately: ```swift @available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.") @Sendable private func $s12TestingTests3foo4TestfMp_17Z152ec26a56a4f672fMu_() async throws -> Void { @Sendable func $s12TestingTests3foo4TestfMp_7__localfMu_(_:isolated (any _Concurrency.Actor)?=Testing.__defaultSynchronousIsolationContext) async throws { _ = unsafe try await Testing.__requiringUnsafe(Testing.__requiringTry(Testing.__requiringAwait(foo()))) } try await $s12TestingTests3foo4TestfMp_7__localfMu_() } @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") @Sendable private func $s12TestingTests3foo4TestfMp_25generator152ec26a56a4f672fMu_() async -> Testing.Test { return .__function( named: "foo()", in: nil as Swift.Never.Type?, xcTestCompatibleSelector: nil, traits: [],sourceBounds: Testing.__SourceBounds(__uncheckedLowerBound: Testing.SourceLocation(__uncheckedFileID: "TestingTests/ZipTests.swift", filePath: "/Volumes/Dev/Source/swift-testing-public/Tests/TestingTests/ZipTests.swift", line: 30, column: 2), upperBound: (30, 20)), parameters: [], testFunction: $s12TestingTests3foo4TestfMp_17Z152ec26a56a4f672fMu_ ) } @section("__DATA_CONST,__swift5_tests") @used @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") private nonisolated let $s12TestingTests3foo4TestfMp_33testContentRecord152ec26a56a4f672fMu_: Testing.__TestContentRecord = ( 0x74657374, /* 'test' */ 0, { outValue, type, _, _ in Testing.Test.__store($s12TestingTests3foo4TestfMp_25generator152ec26a56a4f672fMu_, into: outValue, asTypeAt: type) }, 0, 0 ) ``` Note that the first thunk function has a nested thunk function that serves only to impose actor isolation on the actual test function. This change replaces that thunk with `nonisolated(nonsending)` in appropriate locations throughout the macro target and library target. After this change, we emit a simpler expansion: ```swift @available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.") @Sendable private nonisolated(nonsending) func $s12TestingTests3foo4TestfMp_17Z152ec26a56a4f672fMu_() async throws -> Void { _ = unsafe try await Testing.__requiringUnsafe(Testing.__requiringTry(Testing.__requiringAwait(foo()))) } @available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.") @Sendable private nonisolated(nonsending) func $s12TestingTests3foo4TestfMp_25generator152ec26a56a4f672fMu_() async -> Testing.Test { return .__function( named: "foo()", in: nil as Swift.Never.Type?, xcTestCompatibleSelector: nil, traits: [],sourceBounds: Testing.__SourceBounds(__uncheckedLowerBound: Testing.SourceLocation(__uncheckedFileID: "TestingTests/ZipTests.swift", filePath: "/Volumes/Dev/Source/swift-testing-public/Tests/TestingTests/ZipTests.swift", line: 30, column: 2), upperBound: (30, 20)), parameters: [], testFunction: $s12TestingTests3foo4TestfMp_17Z152ec26a56a4f672fMu_ ) } @section("__DATA_CONST,__swift5_tests") @used @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") private nonisolated let $s12TestingTests3foo4TestfMp_33testContentRecord152ec26a56a4f672fMu_: Testing.__TestContentRecord = ( 0x74657374, /* 'test' */ 0, { outValue, type, _, _ in Testing.Test.__store($s12TestingTests3foo4TestfMp_25generator152ec26a56a4f672fMu_, into: outValue, asTypeAt: type) }, 0, 0 ) ``` --- .../Test.Case.Generator.swift | 14 ++--- .../Testing/Parameterization/Test.Case.swift | 29 +++++++--- Sources/Testing/Running/Runner.swift | 6 +- Sources/Testing/Test+Macro.swift | 14 ++--- Sources/TestingMacros/ConditionMacro.swift | 2 +- .../TestingMacros/SuiteDeclarationMacro.swift | 4 +- .../FunctionDeclSyntaxAdditions.swift | 7 --- .../Support/TestContentGeneration.swift | 2 +- .../TestingMacros/TestDeclarationMacro.swift | 57 ++++--------------- .../TestSupport/TestingAdditions.swift | 10 ++-- Tests/TestingTests/ZipTests.swift | 2 + 11 files changed, 62 insertions(+), 85 deletions(-) diff --git a/Sources/Testing/Parameterization/Test.Case.Generator.swift b/Sources/Testing/Parameterization/Test.Case.Generator.swift index 05467d9bd..697aa2e7b 100644 --- a/Sources/Testing/Parameterization/Test.Case.Generator.swift +++ b/Sources/Testing/Parameterization/Test.Case.Generator.swift @@ -57,7 +57,7 @@ extension Test.Case { /// - Parameters: /// - testFunction: The test function called by the generated test case. init( - testFunction: @escaping @Sendable () async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable () async throws -> Void ) where S == CollectionOfOne { // A beautiful hack to give us the right number of cases: iterate over a // collection containing a single Void value. @@ -85,7 +85,7 @@ extension Test.Case { init( arguments collection: S, parameters: [Test.Parameter], - testFunction: @escaping @Sendable (S.Element) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable (S.Element) async throws -> Void ) where S: Collection { if parameters.count > 1 { self.init(sequence: collection) { element in @@ -124,7 +124,7 @@ extension Test.Case { init( arguments collection1: C1, _ collection2: C2, parameters: [Test.Parameter], - testFunction: @escaping @Sendable (C1.Element, C2.Element) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable (C1.Element, C2.Element) async throws -> Void ) where S == CartesianProduct { self.init(sequence: cartesianProduct(collection1, collection2)) { element in Test.Case(values: [element.0, element.1], parameters: parameters) { @@ -154,7 +154,7 @@ extension Test.Case { private init( sequence: S, parameters: [Test.Parameter], - testFunction: @escaping @Sendable ((E1, E2)) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable ((E1, E2)) async throws -> Void ) where S.Element == (E1, E2), E1: Sendable, E2: Sendable { if parameters.count > 1 { self.init(sequence: sequence) { element in @@ -192,7 +192,7 @@ extension Test.Case { init( arguments collection: S, parameters: [Test.Parameter], - testFunction: @escaping @Sendable ((E1, E2)) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable ((E1, E2)) async throws -> Void ) where S: Collection, S.Element == (E1, E2) { self.init(sequence: collection, parameters: parameters, testFunction: testFunction) } @@ -210,7 +210,7 @@ extension Test.Case { init( arguments zippedCollections: Zip2Sequence, parameters: [Test.Parameter], - testFunction: @escaping @Sendable ((C1.Element, C2.Element)) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable ((C1.Element, C2.Element)) async throws -> Void ) where S == Zip2Sequence, C1: Collection, C2: Collection { self.init(sequence: zippedCollections, parameters: parameters, testFunction: testFunction) } @@ -234,7 +234,7 @@ extension Test.Case { init( arguments dictionary: Dictionary, parameters: [Test.Parameter], - testFunction: @escaping @Sendable ((Key, Value)) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable ((Key, Value)) async throws -> Void ) where S == Dictionary { if parameters.count > 1 { self.init(sequence: dictionary) { element in diff --git a/Sources/Testing/Parameterization/Test.Case.swift b/Sources/Testing/Parameterization/Test.Case.swift index ab9183cf8..d6b750108 100644 --- a/Sources/Testing/Parameterization/Test.Case.swift +++ b/Sources/Testing/Parameterization/Test.Case.swift @@ -155,9 +155,9 @@ extension Test { } } - private init(kind: _Kind, body: @escaping @Sendable () async throws -> Void) { - self._kind = kind - self.body = body + private init(kind: _Kind, body: nonisolated(nonsending) @escaping @Sendable () async throws -> Void) { + _kind = kind + _body = body } /// Initialize a test case for a non-parameterized test function. @@ -166,7 +166,7 @@ extension Test { /// - body: The body closure of this test case. /// /// The resulting test case will have zero arguments. - init(body: @escaping @Sendable () async throws -> Void) { + init(body: nonisolated(nonsending) @escaping @Sendable () async throws -> Void) { self.init(kind: .nonParameterized, body: body) } @@ -180,7 +180,7 @@ extension Test { init( values: [any Sendable], parameters: [Parameter], - body: @escaping @Sendable () async throws -> Void + body: nonisolated(nonsending) @escaping @Sendable () async throws -> Void ) { var isStable = true @@ -229,10 +229,25 @@ extension Test { } /// The body closure of this test case. + private var _body: nonisolated(nonsending) @Sendable () async throws -> Void + + /// Invoke the body closure of this test case. + /// + /// - Parameters: + /// - configuration: The configuration to use for running. /// - /// Do not invoke this closure directly. Always use a ``Runner`` to invoke a + /// Do not call this function directly. Always use a ``Runner`` to invoke a /// test or test case. - var body: @Sendable () async throws -> Void + nonisolated(nonsending) func run(configuration: borrowing Configuration) async throws { + if let actor = configuration.defaultSynchronousIsolationContext { + func runIsolated(to actor: isolated some Actor) async throws { + try await _body() + } + try await runIsolated(to: actor) + } else { + try await _body() + } + } } /// A type representing a single parameter to a parameterized test function. diff --git a/Sources/Testing/Running/Runner.swift b/Sources/Testing/Running/Runner.swift index 9838ad5b9..b5f70bfe3 100644 --- a/Sources/Testing/Running/Runner.swift +++ b/Sources/Testing/Running/Runner.swift @@ -104,7 +104,7 @@ extension Runner { private static func _applyScopingTraits( for test: Test, testCase: Test.Case?, - _ body: @escaping @Sendable () async throws -> Void + _ body: nonisolated(nonsending) @escaping @Sendable () async throws -> Void ) async throws { // If the test does not have any traits, exit early to avoid unnecessary // heap allocations below. @@ -140,7 +140,7 @@ extension Runner { /// /// - Throws: Whatever is thrown by `body` or by any of the traits' provide /// scope function calls. - private static func _applyIssueHandlingTraits(for test: Test, _ body: @escaping @Sendable () async throws -> Void) async throws { + private static func _applyIssueHandlingTraits(for test: Test, _ body: nonisolated(nonsending) @escaping @Sendable () async throws -> Void) async throws { // If the test does not have any traits, exit early to avoid unnecessary // heap allocations below. if test.traits.isEmpty { @@ -420,7 +420,7 @@ extension Runner { try await withTimeLimit(for: step.test, configuration: configuration) { try await _applyScopingTraits(for: step.test, testCase: testCase) { - try await testCase.body() + try await testCase.run(configuration: configuration) } } timeoutHandler: { timeLimit in let issue = Issue( diff --git a/Sources/Testing/Test+Macro.swift b/Sources/Testing/Test+Macro.swift index 97fe104de..1f235b63b 100644 --- a/Sources/Testing/Test+Macro.swift +++ b/Sources/Testing/Test+Macro.swift @@ -163,7 +163,7 @@ extension Test { traits: [any TestTrait], sourceBounds: __SourceBounds, parameters: [__Parameter] = [], - testFunction: @escaping @Sendable () async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable () async throws -> Void ) -> Self where S: ~Copyable & ~Escapable { // Don't use Optional.map here due to a miscompile/crash. Expand out to an // if expression instead. SEE: rdar://134280902 @@ -248,7 +248,7 @@ extension Test { arguments collection: @escaping @Sendable () async throws -> C, sourceBounds: __SourceBounds, parameters paramTuples: [__Parameter], - testFunction: @escaping @Sendable (C.Element) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable (C.Element) async throws -> Void ) -> Self where S: ~Copyable & ~Escapable, C: Collection & Sendable, C.Element: Sendable { let containingTypeInfo: TypeInfo? = if let containingType { TypeInfo(describing: containingType) @@ -395,7 +395,7 @@ extension Test { arguments collection1: @escaping @Sendable () async throws -> C1, _ collection2: @escaping @Sendable () async throws -> C2, sourceBounds: __SourceBounds, parameters paramTuples: [__Parameter], - testFunction: @escaping @Sendable (C1.Element, C2.Element) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable (C1.Element, C2.Element) async throws -> Void ) -> Self where S: ~Copyable & ~Escapable, C1: Collection & Sendable, C1.Element: Sendable, C2: Collection & Sendable, C2.Element: Sendable { let containingTypeInfo: TypeInfo? = if let containingType { TypeInfo(describing: containingType) @@ -423,7 +423,7 @@ extension Test { arguments collection: @escaping @Sendable () async throws -> C, sourceBounds: __SourceBounds, parameters paramTuples: [__Parameter], - testFunction: @escaping @Sendable ((E1, E2)) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable ((E1, E2)) async throws -> Void ) -> Self where S: ~Copyable & ~Escapable, C: Collection & Sendable, C.Element == (E1, E2), E1: Sendable, E2: Sendable { let containingTypeInfo: TypeInfo? = if let containingType { TypeInfo(describing: containingType) @@ -454,7 +454,7 @@ extension Test { arguments dictionary: @escaping @Sendable () async throws -> Dictionary, sourceBounds: __SourceBounds, parameters paramTuples: [__Parameter], - testFunction: @escaping @Sendable ((Key, Value)) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable ((Key, Value)) async throws -> Void ) -> Self where S: ~Copyable & ~Escapable, Key: Sendable, Value: Sendable { let containingTypeInfo: TypeInfo? = if let containingType { TypeInfo(describing: containingType) @@ -479,7 +479,7 @@ extension Test { arguments zippedCollections: @escaping @Sendable () async throws -> Zip2Sequence, sourceBounds: __SourceBounds, parameters paramTuples: [__Parameter], - testFunction: @escaping @Sendable (C1.Element, C2.Element) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable (C1.Element, C2.Element) async throws -> Void ) -> Self where S: ~Copyable & ~Escapable, C1: Collection & Sendable, C1.Element: Sendable, C2: Collection & Sendable, C2.Element: Sendable { let containingTypeInfo: TypeInfo? = if let containingType { TypeInfo(describing: containingType) @@ -547,7 +547,7 @@ extension Test { /// - Warning: This function is used to implement the `@Test` macro. Do not use /// it directly. @_lifetime(copy value) -@inlinable public func __requiringAwait(_ value: consuming T, isolation: isolated (any Actor)? = #isolation) async -> T where T: ~Copyable & ~Escapable { +@inlinable public nonisolated(nonsending) func __requiringAwait(_ value: consuming T) async -> T where T: ~Copyable & ~Escapable { value } diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index a083e55ef..814aaae0b 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -507,7 +507,7 @@ extension ExitTestConditionMacro { } decls.append( """ - @Sendable func \(bodyThunkName)(\(bodyThunkParameterList)) async throws { + @Sendable nonisolated(nonsending) func \(bodyThunkName)(\(bodyThunkParameterList)) async throws { _ = \(applyEffectfulKeywords([.try, .await, .unsafe], to: bodyArgumentExpr))() } """ diff --git a/Sources/TestingMacros/SuiteDeclarationMacro.swift b/Sources/TestingMacros/SuiteDeclarationMacro.swift index 488173448..017e4fc15 100644 --- a/Sources/TestingMacros/SuiteDeclarationMacro.swift +++ b/Sources/TestingMacros/SuiteDeclarationMacro.swift @@ -124,8 +124,8 @@ public struct SuiteDeclarationMacro: PeerMacro, Sendable { let generatorName = context.makeUniqueName("generator") result.append( """ - @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") - @Sendable private \(staticKeyword(for: containingType)) func \(generatorName)() async -> Testing.Test { + @available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.") + @Sendable private nonisolated(nonsending) \(staticKeyword(for: containingType)) func \(generatorName)() async -> Testing.Test { .__type( \(declaration.type.trimmed).self, \(raw: attributeInfo.functionArgumentList(in: context)) diff --git a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift index 8065d299e..8ea344754 100644 --- a/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift +++ b/Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift @@ -27,13 +27,6 @@ extension FunctionDeclSyntax { .contains(.keyword(.mutating)) } - /// Whether or not this function is a `nonisolated` function. - var isNonisolated: Bool { - modifiers.lazy - .map(\.name.tokenKind) - .contains(.keyword(.nonisolated)) - } - /// Whether or not this function declares an operator. var isOperator: Bool { switch name.tokenKind { diff --git a/Sources/TestingMacros/Support/TestContentGeneration.swift b/Sources/TestingMacros/Support/TestContentGeneration.swift index eb4e1123d..e871b2220 100644 --- a/Sources/TestingMacros/Support/TestContentGeneration.swift +++ b/Sources/TestingMacros/Support/TestContentGeneration.swift @@ -69,7 +69,7 @@ func makeTestContentRecordDecl(named name: TokenSyntax, in typeName: TypeSyntax? private nonisolated \(staticKeyword(for: typeName)) let \(name): Testing.__TestContentRecord = ( \(kindExpr), \(kind.commentRepresentation) 0, - \(accessorExpr), + \(raw: accessorExpr), \(contextExpr), 0 ) diff --git a/Sources/TestingMacros/TestDeclarationMacro.swift b/Sources/TestingMacros/TestDeclarationMacro.swift index 667ba05f5..a912d1c81 100644 --- a/Sources/TestingMacros/TestDeclarationMacro.swift +++ b/Sources/TestingMacros/TestDeclarationMacro.swift @@ -292,46 +292,13 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { thunkBody = "_ = \(forwardCall("\(functionDecl.name.trimmed)\(forwardedParamsExpr)"))" } - // If this function is synchronous, is not explicitly nonisolated, and is - // not explicitly isolated to some actor, it should run in the configured - // default isolation context. If the suite type is an actor, this will cause - // a hop off the actor followed by an immediate hop back on, but otherwise - // should be harmless. Note that we do not support specifying an `isolated` - // parameter on a test function at this time. - // - // We use a second, inner thunk function here instead of just adding the - // isolation parameter to the "real" thunk because adding it there prevents - // correct tuple desugaring of the "real" arguments to the thunk. - if functionDecl.signature.effectSpecifiers?.asyncSpecifier == nil && !isMainActorIsolated && !functionDecl.isNonisolated { - // Get a unique name for this secondary thunk. We don't need it to be - // uniqued against functionDecl because it's interior to the "real" thunk, - // so its name can't conflict with any other names visible in this scope. - let isolationThunkName = context.makeUniqueName("") - - // Insert a (defaulted) isolated argument. If we emit a closure (or inner - // function) that captured the arguments to the "real" thunk, the compiler - // has trouble reasoning about the lifetime of arguments to that closure - // especially if those arguments are borrowed or consumed, which results - // in hard-to-avoid compile-time errors. Fortunately, forwarding the full - // argument list is straightforward. - let thunkParamsExprCopy = FunctionParameterClauseSyntax { - for thunkParam in thunkParamsExpr.parameters { - thunkParam - } - FunctionParameterSyntax( - firstName: .wildcardToken(), - type: "isolated (any _Concurrency.Actor)?" as TypeSyntax, - defaultValue: InitializerClauseSyntax(value: "Testing.__defaultSynchronousIsolationContext" as ExprSyntax) - ) - } - - thunkBody = """ - @Sendable func \(isolationThunkName)\(thunkParamsExprCopy) async throws { - \(thunkBody) - } - try await \(isolationThunkName)\(forwardedParamsExpr) - """ - } + // Forward the nonisolated keyword from the original function. If none is + // present, use `nonisolated(nonsending)` by default. + let existingNonisolatedKeyword = functionDecl.modifiers.first { $0.name.tokenKind == .keyword(.nonisolated) } + let nonisolatedKeyword = existingNonisolatedKeyword?.trimmed ?? DeclModifierSyntax( + name: .keyword(.nonisolated), + detail: DeclModifierDetailSyntax(detail: .keyword(.nonsending)) + ) // Add availability guards if needed. thunkBody = createSyntaxNode( @@ -343,7 +310,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { let thunkName = context.makeUniqueName(thunking: functionDecl) let thunkDecl: DeclSyntax = """ @available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.") - @Sendable private \(staticKeyword(for: typeName)) func \(thunkName)\(thunkParamsExpr) async throws -> Void { + @Sendable private \(nonisolatedKeyword) \(staticKeyword(for: typeName)) func \(thunkName)\(thunkParamsExpr) async throws -> Void { \(thunkBody) } """ @@ -434,7 +401,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { result.append( """ @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") - private \(staticKeyword(for: typeName)) nonisolated func \(unavailableTestName)() async -> Testing.Test { + private nonisolated(nonsending) \(staticKeyword(for: typeName)) func \(unavailableTestName)() async -> Testing.Test { .__function( named: \(literal: functionDecl.completeName.trimmedDescription), in: \(typeNameExpr), @@ -459,8 +426,8 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { let generatorName = context.makeUniqueName(thunking: functionDecl, withPrefix: "generator") result.append( """ - @available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.") - @Sendable private \(staticKeyword(for: typeName)) func \(generatorName)() async -> Testing.Test { + @available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.") + @Sendable private nonisolated(nonsending) \(staticKeyword(for: typeName)) func \(generatorName)() async -> Testing.Test { \(raw: testsBody) } """ @@ -489,7 +456,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { """ @available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.") enum \(enumName): Testing.__TestContentRecordContainer { - nonisolated static var __testContentRecord: Testing.__TestContentRecord6_2 { + nonisolated(nonsending) static var __testContentRecord: Testing.__TestContentRecord6_2 { unsafe \(testContentRecordName) } } diff --git a/Tests/TestingTests/TestSupport/TestingAdditions.swift b/Tests/TestingTests/TestSupport/TestingAdditions.swift index 5541c8757..0f2e9dfa8 100644 --- a/Tests/TestingTests/TestSupport/TestingAdditions.swift +++ b/Tests/TestingTests/TestSupport/TestingAdditions.swift @@ -171,7 +171,7 @@ extension Test { sourceLocation: SourceLocation = #_sourceLocation, sourceBounds: __SourceBounds? = nil, name: String = #function, - testFunction: @escaping @Sendable () async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable () async throws -> Void ) { let sourceBounds = sourceBounds ?? __SourceBounds(lowerBoundOnly: sourceLocation) let caseGenerator = Case.Generator(testFunction: testFunction) @@ -203,7 +203,7 @@ extension Test { sourceBounds: __SourceBounds? = nil, column: Int = #column, name: String = #function, - testFunction: @escaping @Sendable (C.Element) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable (C.Element) async throws -> Void ) where C: Collection & Sendable, C.Element: Sendable { let sourceBounds = sourceBounds ?? __SourceBounds(lowerBoundOnly: sourceLocation) let caseGenerator = Case.Generator(arguments: collection, parameters: parameters, testFunction: testFunction) @@ -220,7 +220,7 @@ extension Test { sourceBounds: __SourceBounds? = nil, column: Int = #column, name: String = #function, - testFunction: @escaping @Sendable (C.Element) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable (C.Element) async throws -> Void ) where C: Collection & Sendable, C.Element: Sendable { let sourceBounds = sourceBounds ?? __SourceBounds(lowerBoundOnly: sourceLocation) let caseGenerator = { @Sendable in @@ -255,7 +255,7 @@ extension Test { sourceLocation: SourceLocation = #_sourceLocation, sourceBounds: __SourceBounds? = nil, name: String = #function, - testFunction: @escaping @Sendable (C1.Element, C2.Element) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable (C1.Element, C2.Element) async throws -> Void ) where C1: Collection & Sendable, C1.Element: Sendable, C2: Collection & Sendable, C2.Element: Sendable { let sourceBounds = sourceBounds ?? __SourceBounds(lowerBoundOnly: sourceLocation) let caseGenerator = Case.Generator(arguments: collection1, collection2, parameters: parameters, testFunction: testFunction) @@ -283,7 +283,7 @@ extension Test { sourceLocation: SourceLocation = #_sourceLocation, sourceBounds: __SourceBounds? = nil, name: String = #function, - testFunction: @escaping @Sendable ((C1.Element, C2.Element)) async throws -> Void + testFunction: nonisolated(nonsending) @escaping @Sendable ((C1.Element, C2.Element)) async throws -> Void ) where C1: Collection & Sendable, C1.Element: Sendable, C2: Collection & Sendable, C2.Element: Sendable { let sourceBounds = sourceBounds ?? __SourceBounds(lowerBoundOnly: sourceLocation) let caseGenerator = Case.Generator(arguments: zippedCollections, parameters: parameters, testFunction: testFunction) diff --git a/Tests/TestingTests/ZipTests.swift b/Tests/TestingTests/ZipTests.swift index bd767b222..71fe97c5f 100644 --- a/Tests/TestingTests/ZipTests.swift +++ b/Tests/TestingTests/ZipTests.swift @@ -26,3 +26,5 @@ struct ZipTests { #expect(i == j) } } + + From 541327b3751ccdf90d0f27c732ecd9013c68f4f0 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 25 Feb 2026 17:21:47 -0500 Subject: [PATCH 2/2] That shouldn't be nonsending --- Sources/TestingMacros/TestDeclarationMacro.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TestingMacros/TestDeclarationMacro.swift b/Sources/TestingMacros/TestDeclarationMacro.swift index a912d1c81..5b8989d9b 100644 --- a/Sources/TestingMacros/TestDeclarationMacro.swift +++ b/Sources/TestingMacros/TestDeclarationMacro.swift @@ -456,7 +456,7 @@ public struct TestDeclarationMacro: PeerMacro, Sendable { """ @available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.") enum \(enumName): Testing.__TestContentRecordContainer { - nonisolated(nonsending) static var __testContentRecord: Testing.__TestContentRecord6_2 { + nonisolated static var __testContentRecord: Testing.__TestContentRecord6_2 { unsafe \(testContentRecordName) } }