Skip to content

Commit d62c4ce

Browse files
committed
Unify logging of errors during position conversions
Instead of logging errors in position translation ad-hoc at the caller’s side (and ofter forgetting to do so), log these errors in `LineTable`. To be able to debug where the position conversion error is coming from, also log the file name and line number of the caller. rdar://125545620
1 parent 3e51f70 commit d62c4ce

12 files changed

+405
-117
lines changed

Sources/SKSupport/LineTable.swift

Lines changed: 174 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import LSPLogging
14+
1315
public struct LineTable: Hashable {
1416
@usableFromInline
1517
var impl: [String.Index]
@@ -123,113 +125,242 @@ extension LineTable {
123125
}
124126
}
125127

126-
extension LineTable {
128+
// MARK: - Position translation
127129

128-
// MARK: - Position translation
130+
extension LineTable {
131+
// MARK: line:column <-> String.Index
129132

130-
/// Returns `String.Index` of given logical position.
133+
/// Converts the given UTF-16-based `line:column`` position to a `String.Index`.
134+
///
135+
/// If the position does not refer to a valid position with in the source file, returns `nil` and logs a fault
136+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
131137
///
132138
/// - parameter line: Line number (zero-based).
133139
/// - parameter utf16Column: UTF-16 column offset (zero-based).
134140
@inlinable
135-
public func stringIndexOf(line: Int, utf16Column: Int) -> String.Index? {
141+
public func stringIndexOf(
142+
line: Int,
143+
utf16Column: Int,
144+
callerFile: StaticString = #fileID,
145+
callerLine: UInt = #line
146+
) -> String.Index? {
136147
guard line < count else {
137-
// Line out of range.
148+
logger.fault(
149+
"""
150+
Unable to get string index for \(line):\(utf16Column) (UTF-16) because line is out of range \
151+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
152+
"""
153+
)
138154
return nil
139155
}
140156
let lineSlice = self[line]
141-
return content.utf16.index(lineSlice.startIndex, offsetBy: utf16Column, limitedBy: lineSlice.endIndex)
157+
guard let index = content.utf16.index(lineSlice.startIndex, offsetBy: utf16Column, limitedBy: lineSlice.endIndex)
158+
else {
159+
logger.fault(
160+
"""
161+
Unable to get string index for \(line):\(utf16Column) (UTF-16) because column is out of range \
162+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
163+
"""
164+
)
165+
return nil
166+
}
167+
return index
142168
}
143169

144-
/// Returns `String.Index` of given logical position.
170+
/// Converts the given UTF-8-based `line:column`` position to a `String.Index`.
171+
///
172+
/// If the position does not refer to a valid position with in the source file, returns `nil` and logs a fault
173+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
145174
///
146175
/// - parameter line: Line number (zero-based).
147176
/// - parameter utf8Column: UTF-8 column offset (zero-based).
148177
@inlinable
149-
public func stringIndexOf(line: Int, utf8Column: Int) -> String.Index? {
178+
public func stringIndexOf(
179+
line: Int,
180+
utf8Column: Int,
181+
callerFile: StaticString = #fileID,
182+
callerLine: UInt = #line
183+
) -> String.Index? {
150184
guard 0 <= line, line < count else {
151-
// Line out of range.
185+
logger.fault(
186+
"""
187+
Unable to get string index for \(line):\(utf8Column) (UTF-8) because line is out of range \
188+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
189+
"""
190+
)
152191
return nil
153192
}
154193
guard 0 <= utf8Column else {
155-
// Column out of range.
194+
logger.fault(
195+
"""
196+
Unable to get string index for \(line):\(utf8Column) (UTF-8) because column is out of range \
197+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
198+
"""
199+
)
156200
return nil
157201
}
158202
let lineSlice = self[line]
159203
return content.utf8.index(lineSlice.startIndex, offsetBy: utf8Column, limitedBy: lineSlice.endIndex)
160204
}
161205

162-
/// Returns UTF8 buffer offset of given logical position.
206+
// MARK: line:column <-> UTF-8 offset
207+
208+
/// Converts the given UTF-16-based `line:column`` position to a UTF-8 offset within the source file.
209+
///
210+
/// If the position does not refer to a valid position with in the source file, returns `nil` and logs a fault
211+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
163212
///
164213
/// - parameter line: Line number (zero-based).
165214
/// - parameter utf16Column: UTF-16 column offset (zero-based).
166215
@inlinable
167-
public func utf8OffsetOf(line: Int, utf16Column: Int) -> Int? {
168-
guard let stringIndex = stringIndexOf(line: line, utf16Column: utf16Column) else {
216+
public func utf8OffsetOf(
217+
line: Int,
218+
utf16Column: Int,
219+
callerFile: StaticString = #fileID,
220+
callerLine: UInt = #line
221+
) -> Int? {
222+
guard
223+
let stringIndex = stringIndexOf(
224+
line: line,
225+
utf16Column: utf16Column,
226+
callerFile: callerFile,
227+
callerLine: callerLine
228+
)
229+
else {
169230
return nil
170231
}
171232
return content.utf8.distance(from: content.startIndex, to: stringIndex)
172233
}
173234

174-
/// Returns UTF8 buffer offset of given logical position.
235+
/// Converts the given UTF-8-based `line:column`` position to a UTF-8 offset within the source file.
236+
///
237+
/// If the position does not refer to a valid position with in the source file, returns `nil` and logs a fault
238+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
175239
///
176240
/// - parameter line: Line number (zero-based).
177241
/// - parameter utf8Column: UTF-8 column offset (zero-based).
178242
@inlinable
179-
public func utf8OffsetOf(line: Int, utf8Column: Int) -> Int? {
180-
guard let stringIndex = stringIndexOf(line: line, utf8Column: utf8Column) else {
243+
public func utf8OffsetOf(
244+
line: Int,
245+
utf8Column: Int,
246+
callerFile: StaticString = #fileID,
247+
callerLine: UInt = #line
248+
) -> Int? {
249+
guard
250+
let stringIndex = stringIndexOf(
251+
line: line,
252+
utf8Column: utf8Column,
253+
callerFile: callerFile,
254+
callerLine: callerLine
255+
)
256+
else {
181257
return nil
182258
}
183259
return content.utf8.distance(from: content.startIndex, to: stringIndex)
184260
}
185261

186-
/// Returns logical position of given source offset.
262+
/// Converts the given UTF-16-based line:column position to the UTF-8 offset of that position within the source file.
263+
///
264+
/// If the position does not refer to a valid position with in the snapshot, returns `nil` and logs a fault
265+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
187266
///
188267
/// - parameter utf8Offset: UTF-8 buffer offset (zero-based).
189268
@inlinable
190-
public func lineAndUTF16ColumnOf(utf8Offset: Int) -> (line: Int, utf16Column: Int)? {
269+
public func lineAndUTF16ColumnOf(
270+
utf8Offset: Int,
271+
callerFile: StaticString = #fileID,
272+
callerLine: UInt = #line
273+
) -> (line: Int, utf16Column: Int)? {
191274
guard utf8Offset <= content.utf8.count else {
192-
// Offset ouf of range.
275+
logger.fault(
276+
"""
277+
Unable to get line and UTF-16 column for UTF-8 offset \(utf8Offset) because offset is out of range \
278+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
279+
"""
280+
)
193281
return nil
194282
}
195283
return lineAndUTF16ColumnOf(content.utf8.index(content.startIndex, offsetBy: utf8Offset))
196284
}
197285

198-
@inlinable func lineAndUTF8ColumnOf(utf8Offset: Int) -> (line: Int, utf8Column: Int)? {
199-
guard let (line, utf16Column) = lineAndUTF16ColumnOf(utf8Offset: utf8Offset) else {
286+
/// Converts the given UTF-8-based line:column position to the UTF-8 offset of that position within the source file.
287+
///
288+
/// If the position does not refer to a valid position with in the snapshot, returns `nil` and logs a fault
289+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
290+
@inlinable func lineAndUTF8ColumnOf(
291+
utf8Offset: Int,
292+
callerFile: StaticString = #fileID,
293+
callerLine: UInt = #line
294+
) -> (line: Int, utf8Column: Int)? {
295+
guard
296+
let (line, utf16Column) = lineAndUTF16ColumnOf(
297+
utf8Offset: utf8Offset,
298+
callerFile: callerFile,
299+
callerLine: callerLine
300+
)
301+
else {
200302
return nil
201303
}
202-
guard let utf8Column = utf8ColumnAt(line: line, utf16Column: utf16Column) else {
304+
guard
305+
let utf8Column = utf8ColumnAt(
306+
line: line,
307+
utf16Column: utf16Column,
308+
callerFile: callerFile,
309+
callerLine: callerLine
310+
)
311+
else {
203312
return nil
204313
}
205314
return (line, utf8Column)
206315
}
207316

208-
/// Returns UTF16 column offset at UTF8 version of logical position.
317+
// MARK: UTF-8 line:column <-> UTF-16 line:column
318+
319+
/// Returns UTF-16 column offset at UTF-8 based `line:column` position.
320+
///
321+
/// If the position does not refer to a valid position with in the snapshot, returns `nil` and logs a fault
322+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
209323
///
210324
/// - parameter line: Line number (zero-based).
211325
/// - parameter utf8Column: UTF-8 column offset (zero-based).
212326
@inlinable
213-
public func utf16ColumnAt(line: Int, utf8Column: Int) -> Int? {
327+
public func utf16ColumnAt(
328+
line: Int,
329+
utf8Column: Int,
330+
callerFile: StaticString = #fileID,
331+
callerLine: UInt = #line
332+
) -> Int? {
214333
return convertColumn(
215334
line: line,
216335
column: utf8Column,
217336
indexFunction: content.utf8.index(_:offsetBy:limitedBy:),
218-
distanceFunction: content.utf16.distance(from:to:)
337+
distanceFunction: content.utf16.distance(from:to:),
338+
callerFile: callerFile,
339+
callerLine: callerLine
219340
)
220341
}
221342

222-
/// Returns UTF8 column offset at UTF16 version of logical position.
343+
/// Returns UTF-8 column offset at UTF-16 based `line:column` position.
344+
///
345+
/// If the position does not refer to a valid position with in the snapshot, returns `nil` and logs a fault
346+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
223347
///
224348
/// - parameter line: Line number (zero-based).
225349
/// - parameter utf16Column: UTF-16 column offset (zero-based).
226350
@inlinable
227-
public func utf8ColumnAt(line: Int, utf16Column: Int) -> Int? {
351+
public func utf8ColumnAt(
352+
line: Int,
353+
utf16Column: Int,
354+
callerFile: StaticString = #fileID,
355+
callerLine: UInt = #line
356+
) -> Int? {
228357
return convertColumn(
229358
line: line,
230359
column: utf16Column,
231360
indexFunction: content.utf16.index(_:offsetBy:limitedBy:),
232-
distanceFunction: content.utf8.distance(from:to:)
361+
distanceFunction: content.utf8.distance(from:to:),
362+
callerFile: callerFile,
363+
callerLine: callerLine
233364
)
234365
}
235366

@@ -238,15 +369,27 @@ extension LineTable {
238369
line: Int,
239370
column: Int,
240371
indexFunction: (Substring.Index, Int, Substring.Index) -> Substring.Index?,
241-
distanceFunction: (Substring.Index, Substring.Index) -> Int
372+
distanceFunction: (Substring.Index, Substring.Index) -> Int,
373+
callerFile: StaticString = #fileID,
374+
callerLine: UInt = #line
242375
) -> Int? {
243376
guard line < count else {
244-
// Line out of range.
377+
logger.fault(
378+
"""
379+
Unable to convert column of \(line):\(column) because line is out of range \
380+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
381+
"""
382+
)
245383
return nil
246384
}
247385
let lineSlice = self[line]
248386
guard let targetIndex = indexFunction(lineSlice.startIndex, column, lineSlice.endIndex) else {
249-
// Column out of range
387+
logger.fault(
388+
"""
389+
Unable to convert column of \(line):\(column) because column is out of range \
390+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
391+
"""
392+
)
250393
return nil
251394
}
252395
return distanceFunction(lineSlice.startIndex, targetIndex)

Sources/SourceKitLSP/DocumentManager.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ public struct DocumentSnapshot: Identifiable {
5757
self.language = language
5858
self.lineTable = lineTable
5959
}
60-
61-
func index(of pos: Position) -> String.Index? {
62-
return lineTable.stringIndexOf(line: pos.line, utf16Column: pos.utf16index)
63-
}
6460
}
6561

6662
public final class Document {

Sources/SourceKitLSP/Rename.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,24 @@ fileprivate struct SyntacticRenameName {
266266
}
267267

268268
private extension LineTable {
269-
subscript(range: Range<Position>) -> Substring? {
270-
guard let start = self.stringIndexOf(line: range.lowerBound.line, utf16Column: range.lowerBound.utf16index),
271-
let end = self.stringIndexOf(line: range.upperBound.line, utf16Column: range.upperBound.utf16index)
269+
/// Returns the string in the source file that's with the given position range.
270+
///
271+
/// If either the lower or upper bound of `range` do not refer to valid positions with in the snapshot, returns
272+
/// `nil` and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`).
273+
subscript(range: Range<Position>, callerFile: StaticString = #fileID, callerLine: UInt = #line) -> Substring? {
274+
guard
275+
let start = self.stringIndexOf(
276+
line: range.lowerBound.line,
277+
utf16Column: range.lowerBound.utf16index,
278+
callerFile: callerFile,
279+
callerLine: callerLine
280+
),
281+
let end = self.stringIndexOf(
282+
line: range.upperBound.line,
283+
utf16Column: range.upperBound.utf16index,
284+
callerFile: callerFile,
285+
callerLine: callerLine
286+
)
272287
else {
273288
return nil
274289
}
@@ -1092,7 +1107,6 @@ extension SwiftLanguageService {
10921107
newName: CrossLanguageName
10931108
) async -> [TextEdit] {
10941109
guard let position = snapshot.absolutePosition(of: renameLocation) else {
1095-
logger.fault("Failed to convert \(renameLocation.line):\(renameLocation.utf8Column) to AbsolutePosition")
10961110
return []
10971111
}
10981112
let syntaxTree = await syntaxTreeManager.syntaxTree(for: snapshot)
@@ -1155,7 +1169,6 @@ extension SwiftLanguageService {
11551169
)
11561170

11571171
guard let parameterPosition = snapshot.position(of: parameter.positionAfterSkippingLeadingTrivia) else {
1158-
logger.fault("Failed to convert position of \(parameter.firstName.text) to line-column")
11591172
continue
11601173
}
11611174

@@ -1447,7 +1460,6 @@ fileprivate extension RelatedIdentifiersResponse {
14471460
let position = relatedIdentifier.range.lowerBound
14481461
guard let utf8Column = snapshot.lineTable.utf8ColumnAt(line: position.line, utf16Column: position.utf16index)
14491462
else {
1450-
logger.fault("Unable to find UTF-8 column for \(position.description, privacy: .public)")
14511463
return nil
14521464
}
14531465
return RenameLocation(line: position.line + 1, utf8Column: utf8Column + 1, usage: relatedIdentifier.usage)

Sources/SourceKitLSP/Swift/CodeCompletion.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ extension SwiftLanguageService {
2323
let completionPos = await adjustPositionToStartOfIdentifier(req.position, in: snapshot)
2424

2525
guard let offset = snapshot.utf8Offset(of: completionPos) else {
26-
logger.error(
27-
"invalid completion position \(req.position, privacy: .public) (adjusted: \(completionPos, privacy: .public)"
28-
)
2926
return CompletionList(isIncomplete: true, items: [])
3027
}
3128

@@ -34,7 +31,6 @@ extension SwiftLanguageService {
3431
guard let start = snapshot.indexOf(utf8Offset: offset),
3532
let end = snapshot.index(of: req.position)
3633
else {
37-
logger.error("invalid completion position \(req.position, privacy: .public)")
3834
return CompletionList(isIncomplete: true, items: [])
3935
}
4036

0 commit comments

Comments
 (0)