Skip to content

Commit c753cc4

Browse files
authored
Merge pull request #1126 from ahoppen/ahoppen/jsonrpc-strict-concurrency
Make the `LanguageServerProtocolJSONRPC` module build with strict concurrency enabled
2 parents 8322b9f + d1b527e commit c753cc4

File tree

13 files changed

+336
-318
lines changed

13 files changed

+336
-318
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ let package = Package(
119119
"LanguageServerProtocol",
120120
"LSPLogging",
121121
],
122-
exclude: ["CMakeLists.txt"]
122+
exclude: ["CMakeLists.txt"],
123+
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
123124
),
124125

125126
.testTarget(

Sources/LSPLogging/CustomLogStringConvertible.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Foundation
1515

1616
/// An object that can printed for logging and also offers a redacted description
1717
/// when logging in contexts in which private information shouldn't be captured.
18-
public protocol CustomLogStringConvertible: CustomStringConvertible {
18+
public protocol CustomLogStringConvertible: CustomStringConvertible, Sendable {
1919
/// A full description of the object.
2020
var description: String { get }
2121

@@ -30,7 +30,7 @@ public protocol CustomLogStringConvertible: CustomStringConvertible {
3030
/// There currently is no way to get equivalent functionality in pure Swift. We
3131
/// thus pass this object to OSLog, which just forwards to `description` or
3232
/// `redactedDescription` of an object that implements `CustomLogStringConvertible`.
33-
public class CustomLogStringConvertibleWrapper: NSObject {
33+
public final class CustomLogStringConvertibleWrapper: NSObject, Sendable {
3434
private let underlyingObject: any CustomLogStringConvertible
3535

3636
fileprivate init(_ underlyingObject: any CustomLogStringConvertible) {

Sources/LSPTestSupport/TestJSONRPCConnection.swift

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,55 +20,68 @@ import class Foundation.Pipe
2020
public final class TestJSONRPCConnection {
2121
public let clientToServer: Pipe = Pipe()
2222
public let serverToClient: Pipe = Pipe()
23-
public let client: TestMessageHandler
24-
public let clientConnection: JSONRPCConnection
23+
24+
/// Mocks a client (aka. editor) that can send requests to the LSP server.
25+
public let client: TestClient
26+
27+
/// The connection with which the client can send requests and notifications to the LSP server and using which it
28+
/// receives replies to the requests.
29+
public let clientToServerConnection: JSONRPCConnection
30+
31+
/// Mocks an LSP server that handles requests from the client.
2532
public let server: TestServer
26-
public let serverConnection: JSONRPCConnection
33+
34+
/// The connection with which the server can send requests and notifications to the client and using which it
35+
/// receives replies to the requests.
36+
public let serverToClientConnection: JSONRPCConnection
2737

2838
public init(allowUnexpectedNotification: Bool = true) {
29-
clientConnection = JSONRPCConnection(
39+
clientToServerConnection = JSONRPCConnection(
3040
name: "client",
3141
protocol: testMessageRegistry,
3242
inFD: serverToClient.fileHandleForReading,
3343
outFD: clientToServer.fileHandleForWriting
3444
)
3545

36-
serverConnection = JSONRPCConnection(
46+
serverToClientConnection = JSONRPCConnection(
3747
name: "server",
3848
protocol: testMessageRegistry,
3949
inFD: clientToServer.fileHandleForReading,
4050
outFD: serverToClient.fileHandleForWriting
4151
)
4252

43-
client = TestMessageHandler(server: clientConnection, allowUnexpectedNotification: allowUnexpectedNotification)
44-
server = TestServer(client: serverConnection)
53+
client = TestClient(
54+
connectionToServer: clientToServerConnection,
55+
allowUnexpectedNotification: allowUnexpectedNotification
56+
)
57+
server = TestServer(client: serverToClientConnection)
4558

46-
clientConnection.start(receiveHandler: client) {
59+
clientToServerConnection.start(receiveHandler: client) {
4760
// FIXME: keep the pipes alive until we close the connection. This
4861
// should be fixed systemically.
4962
withExtendedLifetime(self) {}
5063
}
51-
serverConnection.start(receiveHandler: server) {
64+
serverToClientConnection.start(receiveHandler: server) {
5265
// FIXME: keep the pipes alive until we close the connection. This
5366
// should be fixed systemically.
5467
withExtendedLifetime(self) {}
5568
}
5669
}
5770

5871
public func close() {
59-
clientConnection.close()
60-
serverConnection.close()
72+
clientToServerConnection.close()
73+
serverToClientConnection.close()
6174
}
6275
}
6376

6477
public struct TestLocalConnection {
65-
public let client: TestMessageHandler
66-
public let clientConnection: LocalConnection = .init()
78+
public let client: TestClient
79+
public let clientConnection: LocalConnection = LocalConnection()
6780
public let server: TestServer
68-
public let serverConnection: LocalConnection = .init()
81+
public let serverConnection: LocalConnection = LocalConnection()
6982

7083
public init(allowUnexpectedNotification: Bool = true) {
71-
client = TestMessageHandler(server: serverConnection, allowUnexpectedNotification: allowUnexpectedNotification)
84+
client = TestClient(connectionToServer: serverConnection, allowUnexpectedNotification: allowUnexpectedNotification)
7285
server = TestServer(client: clientConnection)
7386

7487
clientConnection.start(handler: client)
@@ -81,18 +94,18 @@ public struct TestLocalConnection {
8194
}
8295
}
8396

84-
public actor TestMessageHandler: MessageHandler {
85-
/// The connection to the language client.
86-
public let server: Connection
97+
public actor TestClient: MessageHandler {
98+
/// The connection to the LSP server.
99+
public let connectionToServer: Connection
87100

88101
private let messageHandlingQueue = AsyncQueue<Serial>()
89102

90103
private var oneShotNotificationHandlers: [((Any) -> Void)] = []
91104

92105
private let allowUnexpectedNotification: Bool
93106

94-
public init(server: Connection, allowUnexpectedNotification: Bool = true) {
95-
self.server = server
107+
public init(connectionToServer: Connection, allowUnexpectedNotification: Bool = true) {
108+
self.connectionToServer = connectionToServer
96109
self.allowUnexpectedNotification = allowUnexpectedNotification
97110
}
98111

@@ -106,7 +119,7 @@ public actor TestMessageHandler: MessageHandler {
106119
}
107120

108121
/// The LSP server sent a notification to the client. Handle it.
109-
public nonisolated func handle(_ notification: some NotificationType, from clientID: ObjectIdentifier) {
122+
public nonisolated func handle(_ notification: some NotificationType) {
110123
messageHandlingQueue.async {
111124
await self.handleNotificationImpl(notification)
112125
}
@@ -125,25 +138,22 @@ public actor TestMessageHandler: MessageHandler {
125138
public nonisolated func handle<Request: RequestType>(
126139
_ request: Request,
127140
id: RequestID,
128-
from clientID: ObjectIdentifier,
129141
reply: @escaping (LSPResult<Request.Response>) -> Void
130142
) {
131143
reply(.failure(.methodNotFound(Request.method)))
132144
}
133-
}
134145

135-
extension TestMessageHandler: Connection {
136146
/// Send a notification to the LSP server.
137147
public nonisolated func send(_ notification: some NotificationType) {
138-
server.send(notification)
148+
connectionToServer.send(notification)
139149
}
140150

141151
/// Send a request to the LSP server and (asynchronously) receive a reply.
142152
public nonisolated func send<Request: RequestType>(
143153
_ request: Request,
144154
reply: @escaping (LSPResult<Request.Response>) -> Void
145155
) -> RequestID {
146-
return server.send(request, reply: reply)
156+
return connectionToServer.send(request, reply: reply)
147157
}
148158
}
149159

@@ -154,35 +164,36 @@ public final class TestServer: MessageHandler {
154164
self.client = client
155165
}
156166

157-
public func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) {
158-
if params is EchoNotification {
159-
self.client.send(params)
167+
/// The client sent a notification to the server. Handle it.
168+
public func handle(_ notification: some NotificationType) {
169+
if notification is EchoNotification {
170+
self.client.send(notification)
160171
} else {
161172
fatalError("Unhandled notification")
162173
}
163174
}
164175

165-
public func handle<R: RequestType>(
166-
_ params: R,
176+
/// The client sent a request to the server. Handle it.
177+
public func handle<Request: RequestType>(
178+
_ request: Request,
167179
id: RequestID,
168-
from clientID: ObjectIdentifier,
169-
reply: @escaping (LSPResult<R.Response>) -> Void
180+
reply: @escaping (LSPResult<Request.Response>) -> Void
170181
) {
171-
if let params = params as? EchoRequest {
172-
reply(.success(params.string as! R.Response))
173-
} else if let params = params as? EchoError {
182+
if let params = request as? EchoRequest {
183+
reply(.success(params.string as! Request.Response))
184+
} else if let params = request as? EchoError {
174185
if let code = params.code {
175186
reply(.failure(ResponseError(code: code, message: params.message!)))
176187
} else {
177-
reply(.success(VoidResponse() as! R.Response))
188+
reply(.success(VoidResponse() as! Request.Response))
178189
}
179190
} else {
180191
fatalError("Unhandled request")
181192
}
182193
}
183194
}
184195

185-
// MARK: Test requests.
196+
// MARK: Test requests
186197

187198
private let testMessageRegistry = MessageRegistry(
188199
requests: [EchoRequest.self, EchoError.self],

Sources/LanguageServerProtocol/Connection.swift

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@ public protocol Connection: AnyObject, Sendable {
2020

2121
/// Send a request and (asynchronously) receive a reply.
2222
func send<Request: RequestType>(
23-
_: Request,
24-
reply: @escaping (LSPResult<Request.Response>) -> Void
23+
_ request: Request,
24+
reply: @escaping @Sendable (LSPResult<Request.Response>) -> Void
2525
) -> RequestID
2626
}
2727

2828
/// An abstract message handler, such as a language server or client.
29-
public protocol MessageHandler: AnyObject {
29+
public protocol MessageHandler: AnyObject, Sendable {
3030

3131
/// Handle a notification without a reply.
3232
///
3333
/// The method should return as soon as the notification has been sufficiently
3434
/// handled to avoid out-of-order requests, e.g. once the notification has
3535
/// been forwarded to clangd.
36-
func handle(_ params: some NotificationType, from clientID: ObjectIdentifier)
36+
func handle(_ notification: some NotificationType)
3737

3838
/// Handle a request and (asynchronously) receive a reply.
3939
///
@@ -42,9 +42,8 @@ public protocol MessageHandler: AnyObject {
4242
/// request has been sent to sourcekitd. The actual semantic computation
4343
/// should occur after the method returns and report the result via `reply`.
4444
func handle<Request: RequestType>(
45-
_ params: Request,
45+
_ request: Request,
4646
id: RequestID,
47-
from clientID: ObjectIdentifier,
4847
reply: @escaping (LSPResult<Request.Response>) -> Void
4948
)
5049
}
@@ -63,7 +62,7 @@ public protocol MessageHandler: AnyObject {
6362
/// ```
6463
///
6564
/// - Note: Unchecked sendable conformance because shared state is guarded by `queue`.
66-
public final class LocalConnection: @unchecked Sendable {
65+
public final class LocalConnection: Connection, @unchecked Sendable {
6766

6867
enum State {
6968
case ready, started, closed
@@ -104,11 +103,9 @@ public final class LocalConnection: @unchecked Sendable {
104103
return .number(_nextRequestID)
105104
}
106105
}
107-
}
108106

109-
extension LocalConnection: Connection {
110107
public func send<Notification>(_ notification: Notification) where Notification: NotificationType {
111-
self.handler?.handle(notification, from: ObjectIdentifier(self))
108+
self.handler?.handle(notification)
112109
}
113110

114111
public func send<Request: RequestType>(
@@ -123,7 +120,7 @@ extension LocalConnection: Connection {
123120
}
124121

125122
precondition(self.state == .started)
126-
handler.handle(request, id: id, from: ObjectIdentifier(self)) { result in
123+
handler.handle(request, id: id) { result in
127124
reply(result)
128125
}
129126

Sources/LanguageServerProtocol/Message.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public protocol _RequestType: MessageType {
2626
func _handle(
2727
_ handler: MessageHandler,
2828
id: RequestID,
29-
connection: Connection,
3029
reply: @escaping (LSPResult<ResponseType>, RequestID) -> Void
3130
)
3231
}
@@ -52,18 +51,17 @@ extension RequestType {
5251
public func _handle(
5352
_ handler: MessageHandler,
5453
id: RequestID,
55-
connection: Connection,
5654
reply: @escaping (LSPResult<ResponseType>, RequestID) -> Void
5755
) {
58-
handler.handle(self, id: id, from: ObjectIdentifier(connection)) { response in
56+
handler.handle(self, id: id) { response in
5957
reply(response.map({ $0 as ResponseType }), id)
6058
}
6159
}
6260
}
6361

6462
extension NotificationType {
65-
public func _handle(_ handler: MessageHandler, connection: Connection) {
66-
handler.handle(self, from: ObjectIdentifier(connection))
63+
public func _handle(_ handler: MessageHandler) {
64+
handler.handle(self)
6765
}
6866
}
6967

0 commit comments

Comments
 (0)