-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove document manager in SwiftLanguageService
#1466
Conversation
@ahoppen I found a lot of places where we reference
Doing so introduces the above new warnings, I'm currently trying to figure out how to resolve these. Also some tests are failing, trying to address those as well. |
guard let sourceKitLSPServer = self.sourceKitLSPServer else { | ||
fatalError("Cannot get document manager because sourceKitLSPServer was deallocated") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not crash the sourcekit-lsp server process if sourceKitLSPServer
got deallocated. It’s a weak variable and it becoming nil
is a valid thing that can happen. Instead, we should throw an error if it becomes nil
, which will return an error response for the requests. You should be able to make this getter throwing by annotating it as get throws
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it throws we'd still have to wrap a try
around documentManger everytime we access that right? arguably the same workload with wrapping a guard let
when marking it as optional like we discussed earlier?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly. I think adding try
is a lot more lightweight than guard let …. else { throw ResponseError.unknown("some message") }
.
Many methods in SwiftLanguageService
throw already anyway (eg. all the calls to documentManager.latestSnapshot
) and for those methods, it’s just a matter of adding the try
keyword if it doesn’t exist yet. For other methods, it should be fairly simple to log the failure by wrapping the access to documentManager
in a orLog
which will log the error and return nil
if getting documentManager
throws.
var documentManager: DocumentManager { | ||
get throws { | ||
guard let sourceKitLSPServer = self.sourceKitLSPServer else { | ||
throw NSError(domain: "SourceKitLSPErrorDomain", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Cannot get document manager because sourceKitLSPServer was deallocated"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally don’t use NSError
in SourceKit-LSP. I would either:
- Define a custom error type that we can throw here or
- follow the precedence set here and return
ResponseError.unknown("Connection to the editor closed")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I feel like ResponseError.unknown would be suffice here, having a custom error seems a bit of an overkill for now
Hey, sorry if this is taking a while, never had a good chance to sit down and dig into this. I'm still experiencing the same issues locally when I run |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. Thank you @louisunlimited! Do you want to mark the PR as ready for review so we can get it merged?
Ah, looks like a merge conflict has crept up. Could you rebase your PR onto |
Ah shoot I just did merge instead of rebase. I can redo that |
b744285
to
f0e8710
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I just found a few more comments. Not sure how I spotted them before. 🙈
@@ -432,7 +437,7 @@ extension SwiftLanguageService { | |||
cancelInFlightPublishDiagnosticsTask(for: notification.textDocument.uri) | |||
await diagnosticReportManager.removeItemsFromCache(with: notification.textDocument.uri) | |||
|
|||
guard let snapshot = self.documentManager.open(notification) else { | |||
guard let snapshot = try? self.documentManager.open(notification) else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you use orLog
here as well?
guard let snapshot = try? self.documentManager.open(notification) else { | |
guard let snapshot = orLog("Getting document manager", { try self.documentManager.open(notification) }) else { |
orLog("Connection to the editor closed") { | ||
try? self.documentManager.close(notification) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the orLog
doesn’t do anything here because documentManager.close
doesn’t actually throw since you’re using try?
.
orLog("Connection to the editor closed") { | |
try? self.documentManager.close(notification) | |
} | |
orLog("Getting document manager") { | |
try self.documentManager.close(notification) | |
} |
@@ -543,7 +550,7 @@ extension SwiftLanguageService { | |||
let replacement: String | |||
} | |||
|
|||
guard let (preEditSnapshot, postEditSnapshot, edits) = self.documentManager.edit(notification) else { | |||
guard let (preEditSnapshot, postEditSnapshot, edits) = try? self.documentManager.edit(notification) else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar here, could you use orLog
so we log an error if the document manager is nil
?
guard let (preEditSnapshot, postEditSnapshot, edits) = try? self.documentManager.edit(notification) else { | |
guard let (preEditSnapshot, postEditSnapshot, edits) = orLog("Getting document manager", { try self.documentManager.edit(notification) } else { |
8596b21
to
1e669d2
Compare
@ahoppen Just made these changes and squashed my commits, I'm not no my personal computer now so I can't test till later today, let me now if you need anything else! Thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s give it a shot 🚀
@swift-ci Please test |
@@ -543,7 +550,7 @@ extension SwiftLanguageService { | |||
let replacement: String | |||
} | |||
|
|||
guard let (preEditSnapshot, postEditSnapshot, edits) = self.documentManager.edit(notification) else { | |||
guard let (preEditSnapshot, postEditSnapshot, edits) = orLog("Getting document manager", { try self.documentManager.edit(notification) }) else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CI says this line exceeds the maximum line lengths 😢 . Could you run swift-format? https://github.com/swiftlang/sourcekit-lsp/blob/main/CONTRIBUTING.md#formatting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just did!
052058e
to
3230d44
Compare
@swift-ci Please test |
@swift-ci Please test Windows |
Yeah these failed test are something I probably need some help with |
The issue was that The solution is that only The following diff fixes the issue and also does a few clean-up, inlining methods that are now only called from one location. Diff
diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift
index 0fcd6b61..9d2f5faf 100644
--- a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift
+++ b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift
@@ -17,6 +17,7 @@ import LanguageServerProtocolJSONRPC
import SKCore
import SKSupport
import SwiftExtensions
+import SwiftSyntax
import struct TSCBasic.AbsolutePath
@@ -443,7 +444,7 @@ extension ClangLanguageService {
// MARK: - Text synchronization
- public func openDocument(_ notification: DidOpenTextDocumentNotification) async {
+ public func openDocument(_ notification: DidOpenTextDocumentNotification, snapshot: DocumentSnapshot) async {
openDocuments[notification.textDocument.uri] = notification.textDocument.language
// Send clangd the build settings for the new file. We need to do this before
// sending the open notification, so that the initial diagnostics already
@@ -459,7 +460,12 @@ extension ClangLanguageService {
func reopenDocument(_ notification: ReopenTextDocumentNotification) {}
- public func changeDocument(_ notification: DidChangeTextDocumentNotification) {
+ public func changeDocument(
+ _ notification: DidChangeTextDocumentNotification,
+ preEditSnapshot: DocumentSnapshot,
+ postEditSnapshot: DocumentSnapshot,
+ edits: [SourceEdit]
+ ) {
clangd.send(notification)
}
diff --git a/Sources/SourceKitLSP/DocumentManager.swift b/Sources/SourceKitLSP/DocumentManager.swift
index 97cfdc62..3d0aaa61 100644
--- a/Sources/SourceKitLSP/DocumentManager.swift
+++ b/Sources/SourceKitLSP/DocumentManager.swift
@@ -204,42 +204,6 @@ public final class DocumentManager: InMemoryDocumentManager, Sendable {
}
}
-extension DocumentManager {
-
- // MARK: - LSP notification handling
-
- /// Convenience wrapper for `open(_:language:version:text:)` that logs on failure.
- @discardableResult
- func open(_ notification: DidOpenTextDocumentNotification) -> DocumentSnapshot? {
- let doc = notification.textDocument
- return orLog("failed to open document", level: .error) {
- try open(doc.uri, language: doc.language, version: doc.version, text: doc.text)
- }
- }
-
- /// Convenience wrapper for `close(_:)` that logs on failure.
- func close(_ notification: DidCloseTextDocumentNotification) {
- orLog("failed to close document", level: .error) {
- try close(notification.textDocument.uri)
- }
- }
-
- /// Convenience wrapper for `edit(_:newVersion:edits:updateDocumentTokens:)`
- /// that logs on failure.
- @discardableResult
- func edit(
- _ notification: DidChangeTextDocumentNotification
- ) -> (preEditSnapshot: DocumentSnapshot, postEditSnapshot: DocumentSnapshot, edits: [SourceEdit])? {
- return orLog("failed to edit document", level: .error) {
- return try edit(
- notification.textDocument.uri,
- newVersion: notification.textDocument.version,
- edits: notification.contentChanges
- )
- }
- }
-}
-
fileprivate extension SourceEdit {
/// Constructs a `SourceEdit` from the given `TextDocumentContentChangeEvent`.
///
diff --git a/Sources/SourceKitLSP/LanguageService.swift b/Sources/SourceKitLSP/LanguageService.swift
index 1b2429e3..638c0d0a 100644
--- a/Sources/SourceKitLSP/LanguageService.swift
+++ b/Sources/SourceKitLSP/LanguageService.swift
@@ -13,6 +13,7 @@
import Foundation
import LanguageServerProtocol
import SKCore
+import SwiftSyntax
/// The state of a `ToolchainLanguageServer`
public enum LanguageServerState {
@@ -113,9 +114,9 @@ public protocol LanguageService: AnyObject, Sendable {
// MARK: - Text synchronization
/// Sent to open up a document on the Language Server.
- /// This may be called before or after a corresponding
- /// `documentUpdatedBuildSettings` call for the same document.
- func openDocument(_ notification: DidOpenTextDocumentNotification) async
+ ///
+ /// This may be called before or after a corresponding `documentUpdatedBuildSettings` call for the same document.
+ func openDocument(_ notification: DidOpenTextDocumentNotification, snapshot: DocumentSnapshot) async
/// Sent to close a document on the Language Server.
func closeDocument(_ notification: DidCloseTextDocumentNotification) async
@@ -127,7 +128,12 @@ public protocol LanguageService: AnyObject, Sendable {
/// Only intended for `SwiftLanguageService`.
func reopenDocument(_ notification: ReopenTextDocumentNotification) async
- func changeDocument(_ notification: DidChangeTextDocumentNotification) async
+ func changeDocument(
+ _ notification: DidChangeTextDocumentNotification,
+ preEditSnapshot: DocumentSnapshot,
+ postEditSnapshot: DocumentSnapshot,
+ edits: [SourceEdit]
+ ) async
func willSaveDocument(_ notification: WillSaveTextDocumentNotification) async
func didSaveDocument(_ notification: DidSaveTextDocumentNotification) async
diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift
index fe41305f..ab176ebe 100644
--- a/Sources/SourceKitLSP/SourceKitLSPServer.swift
+++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift
@@ -1251,7 +1251,18 @@ extension SourceKitLSPServer {
private func openDocument(_ notification: DidOpenTextDocumentNotification, workspace: Workspace) async {
// Immediately open the document even if the build system isn't ready. This is important since
// we check that the document is open when we receive messages from the build system.
- documentManager.open(notification)
+ let snapshot = orLog("Opening document") {
+ try documentManager.open(
+ notification.textDocument.uri,
+ language: notification.textDocument.language,
+ version: notification.textDocument.version,
+ text: notification.textDocument.text
+ )
+ }
+ guard let snapshot else {
+ // Already logged failure
+ return
+ }
let textDocument = notification.textDocument
let uri = textDocument.uri
@@ -1265,7 +1276,7 @@ extension SourceKitLSPServer {
await workspace.buildSystemManager.registerForChangeNotifications(for: uri, language: language)
// If the document is ready, we can immediately send the notification.
- await service.openDocument(notification)
+ await service.openDocument(notification, snapshot: snapshot)
}
func closeDocument(_ notification: DidCloseTextDocumentNotification) async {
@@ -1293,7 +1304,9 @@ extension SourceKitLSPServer {
func closeDocument(_ notification: DidCloseTextDocumentNotification, workspace: Workspace) async {
// Immediately close the document. We need to be sure to clear our pending work queue in case
// the build system still isn't ready.
- documentManager.close(notification)
+ orLog("failed to close document", level: .error) {
+ try documentManager.close(notification.textDocument.uri)
+ }
let uri = notification.textDocument.uri
@@ -1314,8 +1327,23 @@ extension SourceKitLSPServer {
await workspace.semanticIndexManager?.schedulePreparationForEditorFunctionality(of: uri)
// If the document is ready, we can handle the change right now.
- documentManager.edit(notification)
- await workspace.documentService.value[uri]?.changeDocument(notification)
+ let editResult = orLog("Editing document") {
+ try documentManager.edit(
+ notification.textDocument.uri,
+ newVersion: notification.textDocument.version,
+ edits: notification.contentChanges
+ )
+ }
+ guard let (preEditSnapshot, postEditSnapshot, edits) = editResult else {
+ // Already logged failure
+ return
+ }
+ await workspace.documentService.value[uri]?.changeDocument(
+ notification,
+ preEditSnapshot: preEditSnapshot,
+ postEditSnapshot: postEditSnapshot,
+ edits: edits
+ )
}
func willSaveDocument(
diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift
index bf137e15..119e9840 100644
--- a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift
+++ b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift
@@ -430,15 +430,10 @@ extension SwiftLanguageService {
])
}
- public func openDocument(_ notification: DidOpenTextDocumentNotification) async {
+ public func openDocument(_ notification: DidOpenTextDocumentNotification, snapshot: DocumentSnapshot) async {
cancelInFlightPublishDiagnosticsTask(for: notification.textDocument.uri)
await diagnosticReportManager.removeItemsFromCache(with: notification.textDocument.uri)
- guard let snapshot = orLog("Getting document manager", { try self.documentManager.open(notification) }) else {
- // Already logged failure.
- return
- }
-
let buildSettings = await self.buildSettings(for: snapshot.uri)
let req = openDocumentSourcekitdRequest(snapshot: snapshot, compileCommand: buildSettings)
@@ -451,10 +446,6 @@ extension SwiftLanguageService {
inFlightPublishDiagnosticsTasks[notification.textDocument.uri] = nil
await diagnosticReportManager.removeItemsFromCache(with: notification.textDocument.uri)
- orLog("Getting document manager") {
- try self.documentManager.close(notification)
- }
-
let req = closeDocumentSourcekitdRequest(uri: notification.textDocument.uri)
_ = try? await self.sourcekitd.send(req, fileContents: nil)
}
@@ -537,7 +528,12 @@ extension SwiftLanguageService {
}
}
- public func changeDocument(_ notification: DidChangeTextDocumentNotification) async {
+ public func changeDocument(
+ _ notification: DidChangeTextDocumentNotification,
+ preEditSnapshot: DocumentSnapshot,
+ postEditSnapshot: DocumentSnapshot,
+ edits: [SourceEdit]
+ ) async {
cancelInFlightPublishDiagnosticsTask(for: notification.textDocument.uri)
let keys = self.keys
@@ -547,15 +543,6 @@ extension SwiftLanguageService {
let replacement: String
}
- guard
- let (preEditSnapshot, postEditSnapshot, edits) = orLog(
- "Getting document manager",
- { try self.documentManager.edit(notification) }
- )
- else {
- return
- }
-
for edit in edits {
let req = sourcekitd.dictionary([
keys.request: self.requests.editorReplaceText,
@@ -1003,7 +990,6 @@ extension SwiftLanguageService: SKDNotificationHandler {
}
if notification.error == .connectionInterrupted {
- self.state = .connectionInterrupted
await self.setState(.connectionInterrupted)
}
}
|
Head branch was pushed to by a user without write access
Oh thank you so much for this, I just did a quick fix but that introduced some other error; I'll look into this later tomorrow. Appreciate the help! |
Let me know when it’s ready for review again. |
Hi @ahoppen, I just found out it's due to some of my dependencies being outdated, after I pulled all the external deps it builds fine. I was also able to run test and lint, let me know if CI somehow fails again and I can work on that tomorrow! I was actually facing an error in
Interestingly this particular test also fails on a fresh clone of the repo's main branch so I wasn't sure if it's something related to my changes. |
60293cf
to
78dfaf1
Compare
Thanks for letting me know. That test was missing a |
@swift-ci Please test |
@swift-ci Please test Windows |
Uhm why did this CI fail when it says no identified problem. I'll rebase main and see what happens from there |
CI failed with
Could you run swift-format on your changes? https://github.com/swiftlang/sourcekit-lsp/blob/main/CONTRIBUTING.md#formatting |
fix!: remove `documentManager` property fix!: remove `documentManager` in constructor fix: added documentManager as a computed Property fix: remove old documentmanager resetting step fix!: get throws instead of crashing server fix: throw ResponseError instead of NSError fix: adding try & orLog where necesary fix: refined orLog usage style: ran swift-format fix: document being opened twice fix(DocumentManager): remove unwanted comments lint: ran swift format
Yeah that's my bad, some how forgot to commit that file. Should be updated now! |
@swift-ci Please test |
@swift-ci Please test Windows |
SwiftLanguageService
#1423