Skip to content

Commit 7f54c92

Browse files
authored
[6.0 required] Remove UncheckedSendable (#429)
This change removes the `UncheckedSendable` type and adopts `nonisolated(unsafe)`. This change generates a number of diagnostics when built with the Swift 5.10 toolchain due to swiftlang/swift#72070, so we will need to hold this change until we drop 5.10 support. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent ca66809 commit 7f54c92

File tree

9 files changed

+60
-146
lines changed

9 files changed

+60
-146
lines changed

Sources/Testing/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ add_library(Testing
7070
Support/JSON.swift
7171
Support/Locked.swift
7272
Support/SystemError.swift
73-
Support/UncheckedSendable.swift
7473
Support/Versions.swift
7574
Test.ID.Selection.swift
7675
Test.ID.swift

Sources/Testing/EntryPoints/XCTestScaffold.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public enum XCTestScaffold: Sendable {
139139
#endif
140140
#endif
141141
#else
142-
let testCase = UncheckedSendable(rawValue: testCase)
142+
nonisolated(unsafe) let testCase = testCase
143143
#if SWT_TARGET_OS_APPLE
144144
let isProcessLaunchedByXcode = Environment.variable(named: "XCTestSessionIdentifier") != nil
145145
#endif
@@ -152,7 +152,7 @@ public enum XCTestScaffold: Sendable {
152152

153153
// Specify the hosting XCTestCase instance. This value is currently only
154154
// used for exit tests.
155-
let typeName = String(reflecting: type(of: testCase.rawValue as Any))
155+
let typeName = String(reflecting: type(of: testCase as Any))
156156
let functionName = if let parenIndex = functionName.lastIndex(of: "(") {
157157
functionName[..<parenIndex]
158158
} else {
@@ -168,20 +168,20 @@ public enum XCTestScaffold: Sendable {
168168
#if SWT_TARGET_OS_APPLE
169169
if issue.isKnown {
170170
XCTExpectFailure {
171-
testCase.rawValue.record(XCTIssue(issue, processLaunchedByXcode: isProcessLaunchedByXcode))
171+
testCase.record(XCTIssue(issue, processLaunchedByXcode: isProcessLaunchedByXcode))
172172
}
173173
} else {
174-
testCase.rawValue.record(XCTIssue(issue, processLaunchedByXcode: isProcessLaunchedByXcode))
174+
testCase.record(XCTIssue(issue, processLaunchedByXcode: isProcessLaunchedByXcode))
175175
}
176176
#else
177177
// NOTE: XCTestCase.recordFailure(withDescription:inFile:atLine:expected:)
178178
// does not behave as it might appear. The `expected` argument determines
179179
// if the issue represents an assertion failure or a thrown error.
180180
if !issue.isKnown {
181-
testCase.rawValue.recordFailure(withDescription: String(describing: issue),
182-
inFile: issue.sourceLocation?._filePath ?? "<unknown>",
183-
atLine: issue.sourceLocation?.line ?? 0,
184-
expected: true)
181+
testCase.recordFailure(withDescription: String(describing: issue),
182+
inFile: issue.sourceLocation?._filePath ?? "<unknown>",
183+
atLine: issue.sourceLocation?.line ?? 0,
184+
expected: true)
185185
}
186186
#endif
187187
}

Sources/Testing/ExitTests/WaitFor.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ private let _childProcessContinuations = Locked<[pid_t: CheckedContinuation<Exit
5050

5151
/// A condition variable used to suspend the waiter thread created by
5252
/// `_createWaitThread()` when there are no child processes to await.
53-
private let _waitThreadNoChildrenCondition = {
53+
private nonisolated(unsafe) let _waitThreadNoChildrenCondition = {
5454
let result = UnsafeMutablePointer<pthread_cond_t>.allocate(capacity: 1)
5555
_ = pthread_cond_init(result, nil)
56-
return UncheckedSendable(rawValue: result)
56+
return result
5757
}()
5858

5959
/// The implementation of `_createWaitThread()`, run only once.
@@ -89,7 +89,7 @@ private let _createWaitThreadImpl: Void = {
8989
// outside the lock in case acquiring the lock perturbs it.
9090
_childProcessContinuations.withUnsafeUnderlyingLock { lock, childProcessContinuations in
9191
if childProcessContinuations.isEmpty {
92-
_ = pthread_cond_wait(_waitThreadNoChildrenCondition.rawValue, lock)
92+
_ = pthread_cond_wait(_waitThreadNoChildrenCondition, lock)
9393
}
9494
}
9595
}
@@ -171,7 +171,7 @@ func wait(for pid: pid_t) async throws -> ExitCondition {
171171
assert(oldContinuation == nil, "Unexpected continuation found for PID \(pid). Please file a bug report at https://github.com/apple/swift-testing/issues/new")
172172

173173
// Wake up the waiter thread if it is waiting for more child processes.
174-
_ = pthread_cond_signal(_waitThreadNoChildrenCondition.rawValue)
174+
_ = pthread_cond_signal(_waitThreadNoChildrenCondition)
175175
}
176176
}
177177
#endif

Sources/Testing/Running/Configuration.TestFilter.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,10 @@ extension Configuration.TestFilter.Kind {
253253
throw SystemError(description: "Filtering by regular expression matching is unavailable")
254254
}
255255

256-
let regex = UncheckedSendable(rawValue: try Regex(pattern))
256+
nonisolated(unsafe) let regex = try Regex(pattern)
257257
return .function({ test in
258258
let id = String(describing: test.id)
259-
return id.contains(regex.rawValue)
259+
return id.contains(regex)
260260
}, membership: membership)
261261
case let .combination(lhs, rhs, op):
262262
return try .combination(lhs.operation, rhs.operation, op)

Sources/Testing/Support/FileHandle.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal import _TestingInternals
2727
/// This type is not part of the public interface of the testing library.
2828
struct FileHandle: ~Copyable, Sendable {
2929
/// The underlying C file handle.
30-
private var _fileHandle: UncheckedSendable<SWT_FILEHandle>
30+
private nonisolated(unsafe) var _fileHandle: SWT_FILEHandle
3131

3232
/// Whether or not to close `_fileHandle` when this instance is deinitialized.
3333
private var _closeWhenDone: Bool
@@ -105,13 +105,13 @@ struct FileHandle: ~Copyable, Sendable {
105105
/// instance is deinitialized. The caller is responsible for ensuring that
106106
/// there are no other references to `fileHandle` when passing `true`.
107107
init(unsafeCFILEHandle fileHandle: SWT_FILEHandle, closeWhenDone: Bool) {
108-
_fileHandle = UncheckedSendable(rawValue: fileHandle)
108+
_fileHandle = fileHandle
109109
_closeWhenDone = closeWhenDone
110110
}
111111

112112
deinit {
113113
if _closeWhenDone {
114-
fclose(_fileHandle.rawValue)
114+
fclose(_fileHandle)
115115
}
116116
}
117117

@@ -127,7 +127,7 @@ struct FileHandle: ~Copyable, Sendable {
127127
/// Use this function when calling C I/O interfaces such as `fputs()` on the
128128
/// underlying C file handle.
129129
borrowing func withUnsafeCFILEHandle<R>(_ body: (SWT_FILEHandle) throws -> R) rethrows -> R {
130-
try body(_fileHandle.rawValue)
130+
try body(_fileHandle)
131131
}
132132

133133
/// Call a function and pass the underlying POSIX file descriptor to it.

Sources/Testing/Support/Locked.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
6969
}
7070

7171
/// Storage for the underlying lock and wrapped value.
72-
private var _storage: UncheckedSendable<ManagedBuffer<T, _Lock>>
72+
private nonisolated(unsafe) var _storage: ManagedBuffer<T, _Lock>
7373

7474
init(rawValue: T) {
75-
let storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue })
76-
storage.withUnsafeMutablePointerToElements { lock in
75+
_storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue })
76+
_storage.withUnsafeMutablePointerToElements { lock in
7777
#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
7878
_ = pthread_mutex_init(lock, nil)
7979
#elseif os(Windows)
@@ -84,7 +84,6 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
8484
#warning("Platform-specific implementation missing: locking unavailable")
8585
#endif
8686
}
87-
_storage = UncheckedSendable(rawValue: storage)
8887
}
8988

9089
var rawValue: T {
@@ -104,7 +103,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
104103
/// synchronous caller. Wherever possible, use actor isolation or other Swift
105104
/// concurrency tools.
106105
nonmutating func withLock<R>(_ body: (inout T) throws -> R) rethrows -> R {
107-
try _storage.rawValue.withUnsafeMutablePointers { rawValue, lock in
106+
try _storage.withUnsafeMutablePointers { rawValue, lock in
108107
#if SWT_TARGET_OS_APPLE || os(Linux) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
109108
_ = pthread_mutex_lock(lock)
110109
defer {
@@ -148,7 +147,7 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
148147
/// effect is undefined.
149148
nonmutating func withUnsafeUnderlyingLock<R>(_ body: (UnsafeMutablePointer<pthread_mutex_t>, T) throws -> R) rethrows -> R {
150149
try withLock { value in
151-
try _storage.rawValue.withUnsafeMutablePointerToElements { lock in
150+
try _storage.withUnsafeMutablePointerToElements { lock in
152151
try body(lock, value)
153152
}
154153
}

Sources/Testing/Support/UncheckedSendable.swift

Lines changed: 0 additions & 39 deletions
This file was deleted.

Sources/Testing/Test.swift

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -70,26 +70,32 @@ public struct Test: Sendable {
7070

7171
/// An enumeration describing the evaluation state of a test's cases.
7272
///
73-
/// This use of `UncheckedSendable` and of `AnySequence` in this type's cases
74-
/// is necessary because it is not currently possible to express
73+
/// This use of `@unchecked Sendable` and of `AnySequence` in this type's
74+
/// cases is necessary because it is not currently possible to express
7575
/// `Sequence<Test.Case> & Sendable` as an existential (`any`)
7676
/// ([96960993](rdar://96960993)). It is also not possible to have a value of
7777
/// an underlying generic sequence type without specifying its generic
7878
/// parameters.
79-
fileprivate enum TestCasesState: Sendable {
79+
fileprivate enum TestCasesState: @unchecked Sendable {
8080
/// The test's cases have not yet been evaluated.
8181
///
8282
/// - Parameters:
8383
/// - function: The function to call to evaluate the test's cases. The
8484
/// result is a sequence of test cases.
8585
case unevaluated(_ function: @Sendable () async throws -> AnySequence<Test.Case>)
8686

87-
/// The test's cases have been evaluated, and either returned a sequence of
88-
/// cases or failed by throwing an error.
87+
/// The test's cases have been evaluated.
8988
///
9089
/// - Parameters:
91-
/// - result: The result of having evaluated the test's cases.
92-
case evaluated(_ result: Result<UncheckedSendable<AnySequence<Test.Case>>, any Error>)
90+
/// - testCases: The test's cases.
91+
case evaluated(_ testCases: AnySequence<Test.Case>)
92+
93+
/// An error was thrown when the testing library attempted to evaluate the
94+
/// test's cases.
95+
///
96+
/// - Parameters:
97+
/// - error: The thrown error.
98+
case failed(_ error: any Error)
9399
}
94100

95101
/// The evaluation state of this test's cases, if any.
@@ -108,23 +114,18 @@ public struct Test: Sendable {
108114
/// test case is synthesized. For test suite types (as opposed to test
109115
/// functions), the value of this property is `nil`.
110116
var testCases: (some Sequence<Test.Case> & Sendable)? {
111-
guard let testCasesState else {
112-
return nil as AnySequence<Test.Case>?
113-
}
114-
guard case let .evaluated(result) = testCasesState else {
115-
// Callers are expected to first attempt to evaluate a test's cases by
116-
// calling `evaluateTestCases()`.
117-
preconditionFailure("Attempting to access test cases before they have been evaluated.")
118-
}
119-
guard case let .success(testCases) = result else {
120-
// Callers are never expected to access this property after evaluating a
121-
// test's cases, if that evaluation threw an error, because if the test
122-
// cannot be run. In this scenario, a `Runner.Plan` is expected to record
123-
// issue for the test, rather than attempt to run it, and thus never
124-
// access this property.
125-
preconditionFailure("Attempting to access test cases after evaluating them failed.")
117+
testCasesState.map { testCasesState in
118+
guard case let .evaluated(testCases) = testCasesState else {
119+
// Callers are expected to first attempt to evaluate a test's cases by
120+
// calling `evaluateTestCases()`, and are never expected to access this
121+
// property after evaluating a test's cases if that evaluation threw an
122+
// error (because the test cannot be run.) If an error was thrown, a
123+
// `Runner.Plan` is expected to record issue for the test, rather than
124+
// attempt to run it, and thus never access this property.
125+
preconditionFailure("Attempting to access test cases with invalid state. Please file a bug report at https://github.com/apple/swift-testing/issues/new and include this information: \(String(reflecting: testCasesState))")
126+
}
127+
return testCases
126128
}
127-
return testCases.rawValue
128129
}
129130

130131
/// Evaluate this test's cases if they have not been evaluated yet.
@@ -138,20 +139,14 @@ public struct Test: Sendable {
138139
///
139140
/// - Throws: Any error caught while first evaluating the test arguments.
140141
mutating func evaluateTestCases() async throws {
141-
guard let testCasesState else { return }
142-
143-
do {
144-
switch testCasesState {
145-
case let .unevaluated(function):
142+
if case let .unevaluated(function) = testCasesState {
143+
do {
146144
let sequence = try await function()
147-
self.testCasesState = .evaluated(.success(UncheckedSendable(rawValue: sequence)))
148-
case .evaluated:
149-
// No-op: already evaluated
150-
break
145+
self.testCasesState = .evaluated(sequence)
146+
} catch {
147+
self.testCasesState = .failed(error)
148+
throw error
151149
}
152-
} catch {
153-
self.testCasesState = .evaluated(.failure(error))
154-
throw error
155150
}
156151
}
157152

@@ -242,7 +237,7 @@ public struct Test: Sendable {
242237
self.sourceLocation = sourceLocation
243238
self.containingTypeInfo = containingTypeInfo
244239
self.xcTestCompatibleSelector = xcTestCompatibleSelector
245-
self.testCasesState = .evaluated(.success(UncheckedSendable(rawValue: .init(testCases))))
240+
self.testCasesState = .evaluated(.init(testCases))
246241
self.parameters = parameters
247242
}
248243
}
@@ -348,14 +343,12 @@ extension Test {
348343
_timeLimit = test.timeLimit.map(TimeValue.init)
349344
}
350345

351-
testCases = switch test.testCasesState {
352-
case .unevaluated,
353-
.evaluated(.failure):
354-
[]
355-
case let .evaluated(.success(testCases)):
356-
testCases.rawValue.map(Test.Case.Snapshot.init(snapshotting:))
357-
case nil:
358-
nil
346+
testCases = test.testCasesState.map { testCasesState in
347+
if case let .evaluated(testCases) = testCasesState {
348+
testCases.map(Test.Case.Snapshot.init(snapshotting:))
349+
} else {
350+
[]
351+
}
359352
}
360353
}
361354

Tests/TestingTests/Support/UncheckedSendableTests.swift

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)