Skip to content

Commit a25b1d2

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 a25b1d2

12 files changed

+406
-117
lines changed

Sources/SKSupport/LineTable.swift

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

13+
import LSPLogging
14+
import os
15+
1316
public struct LineTable: Hashable {
1417
@usableFromInline
1518
var impl: [String.Index]
@@ -123,113 +126,242 @@ extension LineTable {
123126
}
124127
}
125128

126-
extension LineTable {
129+
// MARK: - Position translation
127130

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

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

144-
/// Returns `String.Index` of given logical position.
171+
/// Converts the given UTF-8-based `line:column`` position to a `String.Index`.
172+
///
173+
/// If the position does not refer to a valid position with in the source file, returns `nil` and logs a fault
174+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
145175
///
146176
/// - parameter line: Line number (zero-based).
147177
/// - parameter utf8Column: UTF-8 column offset (zero-based).
148178
@inlinable
149-
public func stringIndexOf(line: Int, utf8Column: Int) -> String.Index? {
179+
public func stringIndexOf(
180+
line: Int,
181+
utf8Column: Int,
182+
callerFile: StaticString = #fileID,
183+
callerLine: UInt = #line
184+
) -> String.Index? {
150185
guard 0 <= line, line < count else {
151-
// Line out of range.
186+
logger.fault(
187+
"""
188+
Unable to get string index for \(line):\(utf8Column) because line is out of range \
189+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
190+
"""
191+
)
152192
return nil
153193
}
154194
guard 0 <= utf8Column else {
155-
// Column out of range.
195+
logger.fault(
196+
"""
197+
Unable to get string index for \(line):\(utf8Column) because column is out of range \
198+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
199+
"""
200+
)
156201
return nil
157202
}
158203
let lineSlice = self[line]
159204
return content.utf8.index(lineSlice.startIndex, offsetBy: utf8Column, limitedBy: lineSlice.endIndex)
160205
}
161206

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

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

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

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

208-
/// Returns UTF16 column offset at UTF8 version of logical position.
318+
// MARK: UTF-8 line:column <-> UTF-16 line:column
319+
320+
/// Returns UTF-16 column offset at UTF-8 based `line:column` position.
321+
///
322+
/// If the position does not refer to a valid position with in the snapshot, returns `nil` and logs a fault
323+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
209324
///
210325
/// - parameter line: Line number (zero-based).
211326
/// - parameter utf8Column: UTF-8 column offset (zero-based).
212327
@inlinable
213-
public func utf16ColumnAt(line: Int, utf8Column: Int) -> Int? {
328+
public func utf16ColumnAt(
329+
line: Int,
330+
utf8Column: Int,
331+
callerFile: StaticString = #fileID,
332+
callerLine: UInt = #line
333+
) -> Int? {
214334
return convertColumn(
215335
line: line,
216336
column: utf8Column,
217337
indexFunction: content.utf8.index(_:offsetBy:limitedBy:),
218-
distanceFunction: content.utf16.distance(from:to:)
338+
distanceFunction: content.utf16.distance(from:to:),
339+
callerFile: callerFile,
340+
callerLine: callerLine
219341
)
220342
}
221343

222-
/// Returns UTF8 column offset at UTF16 version of logical position.
344+
/// Returns UTF-8 column offset at UTF-16 based `line:column` position.
345+
///
346+
/// If the position does not refer to a valid position with in the snapshot, returns `nil` and logs a fault
347+
/// containing the file and line of the caller (from `callerFile` and `callerLine`).
223348
///
224349
/// - parameter line: Line number (zero-based).
225350
/// - parameter utf16Column: UTF-16 column offset (zero-based).
226351
@inlinable
227-
public func utf8ColumnAt(line: Int, utf16Column: Int) -> Int? {
352+
public func utf8ColumnAt(
353+
line: Int,
354+
utf16Column: Int,
355+
callerFile: StaticString = #fileID,
356+
callerLine: UInt = #line
357+
) -> Int? {
228358
return convertColumn(
229359
line: line,
230360
column: utf16Column,
231361
indexFunction: content.utf16.index(_:offsetBy:limitedBy:),
232-
distanceFunction: content.utf8.distance(from:to:)
362+
distanceFunction: content.utf8.distance(from:to:),
363+
callerFile: callerFile,
364+
callerLine: callerLine
233365
)
234366
}
235367

@@ -238,15 +370,27 @@ extension LineTable {
238370
line: Int,
239371
column: Int,
240372
indexFunction: (Substring.Index, Int, Substring.Index) -> Substring.Index?,
241-
distanceFunction: (Substring.Index, Substring.Index) -> Int
373+
distanceFunction: (Substring.Index, Substring.Index) -> Int,
374+
callerFile: StaticString = #fileID,
375+
callerLine: UInt = #line
242376
) -> Int? {
243377
guard line < count else {
244-
// Line out of range.
378+
logger.fault(
379+
"""
380+
Unable to convert column of \(line):\(column) because line is out of range \
381+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
382+
"""
383+
)
245384
return nil
246385
}
247386
let lineSlice = self[line]
248387
guard let targetIndex = indexFunction(lineSlice.startIndex, column, lineSlice.endIndex) else {
249-
// Column out of range
388+
logger.fault(
389+
"""
390+
Unable to convert column of \(line):\(column) because column is out of range \
391+
(\(callerFile, privacy: .public):\(callerLine, privacy: .public))
392+
"""
393+
)
250394
return nil
251395
}
252396
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)