From 96247c047212908d5ed4da9a18024ab534cbbc17 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Wed, 12 Feb 2025 21:01:54 -0800 Subject: [PATCH] Add sendable annotation to `userInfo` closure swift-foundation recently landed a change (in swiftlang/swift-foundation#764) which requires `any Sendable` values in `JSONEncoder.userInfo`. This causes a build failure: ``` JSONRPCConnection.swift:370:50: error: type '(RequestID) -> Optional' does not conform to the 'Sendable' protocol 368 | 369 | // Setup callback for response type. 370 | decoder.userInfo[.responseTypeCallbackKey] = { (id: RequestID) -> ResponseType.Type? in | |- error: type '(RequestID) -> Optional' does not conform to the 'Sendable' protocol | `- note: a function type must be marked '@Sendable' to conform to 'Sendable' 371 | guard let outstanding = self.outstandingRequests[id] else { 372 | logger.error("Unknown request for \(id, privacy: .public)") ``` Make the closure sendable, which is safe as we're only reading from `outstandingRequests` (where all our writes are guarded by the same queue that the decoding is on). --- .../JSONRPCConnection.swift | 4 +++- .../LanguageServerProtocolJSONRPC/MessageCoding.swift | 2 +- Sources/SKTestSupport/CheckCoding.swift | 2 +- .../CodingTests.swift | 10 ++++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Sources/LanguageServerProtocolJSONRPC/JSONRPCConnection.swift b/Sources/LanguageServerProtocolJSONRPC/JSONRPCConnection.swift index 1b6ea2549..6c677d992 100644 --- a/Sources/LanguageServerProtocolJSONRPC/JSONRPCConnection.swift +++ b/Sources/LanguageServerProtocolJSONRPC/JSONRPCConnection.swift @@ -367,7 +367,9 @@ public final class JSONRPCConnection: Connection { decoder.userInfo[.messageRegistryKey] = messageRegistry // Setup callback for response type. - decoder.userInfo[.responseTypeCallbackKey] = { (id: RequestID) -> ResponseType.Type? in + decoder.userInfo[.responseTypeCallbackKey] = { @Sendable (id: RequestID) -> ResponseType.Type? in + // `outstandingRequests` should never be mutated in this closure. Reading is fine as all of our other writes are + // guarded by `queue`, but `JSONDecoder` could (since this is sendable) invoke this concurrently. guard let outstanding = self.outstandingRequests[id] else { logger.error("Unknown request for \(id, privacy: .public)") return nil diff --git a/Sources/LanguageServerProtocolJSONRPC/MessageCoding.swift b/Sources/LanguageServerProtocolJSONRPC/MessageCoding.swift index f11aa7453..b50513cbb 100644 --- a/Sources/LanguageServerProtocolJSONRPC/MessageCoding.swift +++ b/Sources/LanguageServerProtocolJSONRPC/MessageCoding.swift @@ -34,7 +34,7 @@ extension CodingUserInfoKey { extension JSONRPCMessage: Codable { - public typealias ResponseTypeCallback = (RequestID) -> ResponseType.Type? + public typealias ResponseTypeCallback = @Sendable (RequestID) -> ResponseType.Type? private enum CodingKeys: String, CodingKey { case jsonrpc diff --git a/Sources/SKTestSupport/CheckCoding.swift b/Sources/SKTestSupport/CheckCoding.swift index 87d52ef9e..cceb9735e 100644 --- a/Sources/SKTestSupport/CheckCoding.swift +++ b/Sources/SKTestSupport/CheckCoding.swift @@ -95,7 +95,7 @@ package func checkDecoding( package func checkCoding( _ value: T, json: String, - userInfo: [CodingUserInfoKey: Any] = [:], + userInfo: [CodingUserInfoKey: any Sendable] = [:], file: StaticString = #filePath, line: UInt = #line, body: (T) -> Void diff --git a/Tests/LanguageServerProtocolJSONRPCTests/CodingTests.swift b/Tests/LanguageServerProtocolJSONRPCTests/CodingTests.swift index 46f02a890..05ba6bfe9 100644 --- a/Tests/LanguageServerProtocolJSONRPCTests/CodingTests.swift +++ b/Tests/LanguageServerProtocolJSONRPCTests/CodingTests.swift @@ -283,7 +283,7 @@ final class CodingTests: XCTestCase { return $0 == .string("unknown") ? nil : InitializeResult.self } - var info = defaultCodingInfo as [CodingUserInfoKey: Any] + var info = defaultCodingInfo info[CodingUserInfoKey.responseTypeCallbackKey] = responseTypeCallback checkMessageDecodingError( @@ -366,7 +366,9 @@ final class CodingTests: XCTestCase { } } -let defaultCodingInfo = [CodingUserInfoKey.messageRegistryKey: MessageRegistry.lspProtocol] +let defaultCodingInfo: [CodingUserInfoKey: any Sendable] = [ + CodingUserInfoKey.messageRegistryKey: MessageRegistry.lspProtocol +] private func checkMessageCoding( _ value: Request, @@ -417,7 +419,7 @@ private func checkMessageCoding( return $0 == .string("unknown") ? nil : Response.self } - var codingInfo = defaultCodingInfo as [CodingUserInfoKey: Any] + var codingInfo = defaultCodingInfo codingInfo[.responseTypeCallbackKey] = callback checkCoding(JSONRPCMessage.response(value, id: id), json: json, userInfo: codingInfo, file: file, line: line) { @@ -462,7 +464,7 @@ private func checkMessageCoding( private func checkMessageDecodingError( _ expected: MessageDecodingError, json: String, - userInfo: [CodingUserInfoKey: Any] = defaultCodingInfo, + userInfo: [CodingUserInfoKey: any Sendable] = defaultCodingInfo, file: StaticString = #filePath, line: UInt = #line ) {