@@ -16,6 +16,9 @@ import SourceKitD
16
16
import SwiftParserDiagnostics
17
17
18
18
actor DiagnosticReportManager {
19
+ /// A task that gets report from sourcekitd
20
+ private typealias ReportTask = Task < RelatedFullDocumentDiagnosticReport , Error >
21
+
19
22
private let sourcekitd : SourceKitD
20
23
private let syntaxTreeManager : SyntaxTreeManager
21
24
private let documentManager : DocumentManager
@@ -24,19 +27,19 @@ actor DiagnosticReportManager {
24
27
private nonisolated var keys : sourcekitd_keys { return sourcekitd. keys }
25
28
private nonisolated var requests : sourcekitd_requests { return sourcekitd. requests }
26
29
27
- /// The cache that stores reports for snapshot id and buildSettings
30
+ /// The cache that stores reportTasks for snapshot id and buildSettings
28
31
///
29
32
/// Conceptually, this is a dictionary. To prevent excessive memory usage we
30
33
/// only keep `cacheSize` entries within the array. Older entries are at the
31
34
/// end of the list, newer entries at the front.
32
- private var reportCache :
35
+ private var reportTaskCache :
33
36
[ (
34
37
snapshotID: DocumentSnapshot . ID ,
35
38
buildSettings: SwiftCompileCommand ? ,
36
- report : RelatedFullDocumentDiagnosticReport
39
+ reportTask : ReportTask
37
40
) ] = [ ]
38
41
39
- /// The number of reports to keep
42
+ /// The number of reportTasks to keep
40
43
///
41
44
/// - Note: This has been chosen without scientific measurements.
42
45
private let cacheSize = 5
@@ -53,46 +56,39 @@ actor DiagnosticReportManager {
53
56
self . clientHasDiagnosticsCodeDescriptionSupport = clientHasDiagnosticsCodeDescriptionSupport
54
57
}
55
58
56
- func diagnosticReport( for snapshot: DocumentSnapshot , buildSettings: SwiftCompileCommand ? , useCache: Bool = false )
57
- async throws -> RelatedFullDocumentDiagnosticReport
58
- {
59
- if useCache, let report = report ( for: snapshot. id, buildSettings: buildSettings) {
60
- return report
61
- }
62
- guard let buildSettings = buildSettings else {
63
- logger. log (
64
- " Producing syntactic diagnostics from the built-in swift-syntax because we don't have buildSettings "
65
- )
66
- // If we don't have build settings,
67
- // sourcekitd won't be able to give us accurate semantic diagnostics.
68
- // Fall back to providing syntactic diagnostics from the built-in
69
- // swift-syntax. That's the best we can do for now.
70
- let report = try await requestFallbackReport ( with: snapshot)
71
- setReport ( for: snapshot. id, buildSettings: nil , report: report)
59
+ func diagnosticReport(
60
+ for snapshot: DocumentSnapshot ,
61
+ buildSettings: SwiftCompileCommand ? ,
62
+ useCache: Bool = false
63
+ ) async throws -> RelatedFullDocumentDiagnosticReport {
64
+ if useCache, let report = try await reportTask ( for: snapshot. id, buildSettings: buildSettings) ? . value {
72
65
return report
73
66
}
74
- guard !buildSettings. isFallback else {
67
+ let reportTask: Task< RelatedFullDocumentDiagnosticReport, Error>
68
+ if let buildSettings, !buildSettings. isFallback {
69
+ reportTask = Task {
70
+ return try await requestReport ( with: snapshot, compilerArgs: buildSettings. compilerArgs)
71
+ }
72
+ } else {
75
73
logger. log (
76
- " Producing syntactic diagnostics from the built-in swift-syntax because we have fallback arguments "
74
+ " Producing syntactic diagnostics from the built-in swift-syntax because we \( buildSettings != nil ? " have fallback build settings " : " don't have build settings " ) "
77
75
)
78
- // If we only have fallback build settings,
76
+ // If we don't have build settings or we only have fallback build settings,
79
77
// sourcekitd won't be able to give us accurate semantic diagnostics.
80
78
// Fall back to providing syntactic diagnostics from the built-in
81
79
// swift-syntax. That's the best we can do for now.
82
- let report = try await requestFallbackReport ( with : snapshot )
83
- setReport ( for : snapshot . id , buildSettings : buildSettings , report : report )
84
- return report
80
+ reportTask = Task {
81
+ return try await requestFallbackReport ( with : snapshot )
82
+ }
85
83
}
86
- let report = try await requestReport ( with: snapshot, compilerArgs: buildSettings. compilerArgs)
87
- setReport ( for: snapshot. id, buildSettings: buildSettings, report: report)
88
- return report
84
+ setReportTask( for: snapshot. id, buildSettings: buildSettings, reportTask: reportTask)
85
+ return try await reportTask. value
89
86
}
90
- }
91
87
92
- private extension DiagnosticReportManager {
93
- func requestReport ( with snapshot: DocumentSnapshot , compilerArgs : [ String ] ) async throws
94
- -> LanguageServerProtocol . RelatedFullDocumentDiagnosticReport
95
- {
88
+ private func requestReport (
89
+ with snapshot: DocumentSnapshot,
90
+ compilerArgs : [ String ]
91
+ ) async throws -> LanguageServerProtocol . RelatedFullDocumentDiagnosticReport {
96
92
try Task . checkCancellation ( )
97
93
98
94
let keys = self . keys
@@ -111,13 +107,16 @@ private extension DiagnosticReportManager {
111
107
// Check that the document wasn't modified while we were getting diagnostics. This could happen because we are
112
108
// calling `fullDocumentDiagnosticReport` from `publishDiagnosticsIfNeeded` outside of `messageHandlingQueue`
113
109
// and thus a concurrent edit is possible while we are waiting for the sourcekitd request to return a result.
114
- throw ResponseError . unknown ( " Document was modified while loading document " )
110
+ throw ResponseError . unknown ( " Document was modified while loading diagnostics " )
115
111
}
116
112
117
- let supportsCodeDescription = self . clientHasDiagnosticsCodeDescriptionSupport
118
113
var diagnostics : [ Diagnostic ] = [ ]
119
114
dict [ keys. diagnostics] ? . forEach { _, diag in
120
- if let diag = Diagnostic ( diag, in: snapshot, useEducationalNoteAsCode: supportsCodeDescription) {
115
+ if let diag = Diagnostic (
116
+ diag,
117
+ in: snapshot,
118
+ useEducationalNoteAsCode: self . clientHasDiagnosticsCodeDescriptionSupport
119
+ ) {
121
120
diagnostics. append ( diag)
122
121
}
123
122
return true
@@ -126,9 +125,9 @@ private extension DiagnosticReportManager {
126
125
return RelatedFullDocumentDiagnosticReport ( items: diagnostics)
127
126
}
128
127
129
- func requestFallbackReport( with snapshot : DocumentSnapshot ) async throws
130
- -> LanguageServerProtocol . RelatedFullDocumentDiagnosticReport
131
- {
128
+ private func requestFallbackReport(
129
+ with snapshot : DocumentSnapshot
130
+ ) async throws -> LanguageServerProtocol . RelatedFullDocumentDiagnosticReport {
132
131
// If we don't have build settings or we only have fallback build settings,
133
132
// sourcekitd won't be able to give us accurate semantic diagnostics.
134
133
// Fall back to providing syntactic diagnostics from the built-in
@@ -145,31 +144,33 @@ private extension DiagnosticReportManager {
145
144
return RelatedFullDocumentDiagnosticReport ( items: diagnostics)
146
145
}
147
146
148
- /// The report for the given document snapshot and buildSettings.
149
- func report( for snapshotID: DocumentSnapshot . ID , buildSettings: SwiftCompileCommand ? )
150
- -> RelatedFullDocumentDiagnosticReport ?
151
- {
152
- return reportCache. first ( where: { $0. snapshotID == snapshotID && $0. buildSettings == buildSettings } ) ? . report
147
+ /// The reportTask for the given document snapshot and buildSettings.
148
+ private func reportTask(
149
+ for snapshotID: DocumentSnapshot . ID ,
150
+ buildSettings: SwiftCompileCommand ?
151
+ ) -> ReportTask ? {
152
+ return reportTaskCache. first ( where: { $0. snapshotID == snapshotID && $0. buildSettings == buildSettings } ) ?
153
+ . reportTask
153
154
}
154
155
155
- /// Set the report for the given document snapshot and buildSettings.
156
+ /// Set the reportTask for the given document snapshot and buildSettings.
156
157
///
157
158
/// If we are already storing `cacheSize` many reports, the oldest one
158
159
/// will get discarded.
159
- func setReport (
160
+ private func setReportTask (
160
161
for snapshotID: DocumentSnapshot . ID ,
161
162
buildSettings: SwiftCompileCommand ? ,
162
- report : RelatedFullDocumentDiagnosticReport
163
+ reportTask : ReportTask
163
164
) {
164
- reportCache . insert ( ( snapshotID, buildSettings, report ) , at: 0 )
165
+ reportTaskCache . insert ( ( snapshotID, buildSettings, reportTask ) , at: 0 )
165
166
166
- // Remove any reports for old versions of this document.
167
- reportCache . removeAll ( where: { $0. snapshotID < snapshotID } )
167
+ // Remove any reportTasks for old versions of this document.
168
+ reportTaskCache . removeAll ( where: { $0. snapshotID < snapshotID } )
168
169
169
- // If we still have more than `cacheSize` reports , delete the ones that
170
+ // If we still have more than `cacheSize` reportTasks , delete the ones that
170
171
// were produced last. We can always re-request them on-demand.
171
- while reportCache . count > cacheSize {
172
- reportCache . removeLast ( )
172
+ while reportTaskCache . count > cacheSize {
173
+ reportTaskCache . removeLast ( )
173
174
}
174
175
}
175
176
}
0 commit comments