Skip to content

Commit 1d78d55

Browse files
authored
Merge pull request swiftlang#15 from rintaro/completion-adjustlocation-fix
[Completion] Fix crash for completion on identifier at the start of file
2 parents 3274eed + d52254c commit 1d78d55

File tree

3 files changed

+57
-17
lines changed

3 files changed

+57
-17
lines changed

Sources/SourceKit/sourcekitd/SwiftLanguageServer.swift

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,11 @@ extension SwiftLanguageServer {
300300
return
301301
}
302302

303-
let completionPos = adjustCompletionLocation(req.params.position, in: snapshot)
303+
guard let completionPos = adjustCompletionLocation(req.params.position, in: snapshot) else {
304+
log("invalid completion position \(req.params.position)")
305+
req.reply(CompletionList(isIncomplete: true, items: []))
306+
return
307+
}
304308

305309
guard let offset = snapshot.utf8Offset(of: completionPos) else {
306310
log("invalid completion position \(req.params.position) (adjusted: \(completionPos)")
@@ -387,29 +391,35 @@ extension SwiftLanguageServer {
387391
return result
388392
}
389393

390-
func adjustCompletionLocation(_ pos: Position, in snapshot: DocumentSnapshot) -> Position {
391-
guard let requestedLoc = snapshot.index(of: pos), requestedLoc != snapshot.text.startIndex else {
392-
return pos
394+
/// Adjust completion position to the start of identifier characters.
395+
func adjustCompletionLocation(_ pos: Position, in snapshot: DocumentSnapshot) -> Position? {
396+
guard pos.line < snapshot.lineTable.count else {
397+
// Line out of range.
398+
return nil
393399
}
400+
let lineSlice = snapshot.lineTable[pos.line]
401+
let startIndex = lineSlice.startIndex
394402

395403
let identifierChars = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "_"))
396404

397-
var prev = requestedLoc
398-
var loc = snapshot.text.index(before: requestedLoc)
399-
while identifierChars.contains(snapshot.text[loc].unicodeScalars.first!) {
400-
prev = loc
401-
loc = snapshot.text.index(before: loc)
405+
guard var loc = lineSlice.utf16.index(startIndex, offsetBy: pos.utf16index, limitedBy: lineSlice.endIndex) else {
406+
// Column out of range.
407+
return nil
408+
}
409+
while loc != startIndex {
410+
let prev = lineSlice.index(before: loc)
411+
if !identifierChars.contains(lineSlice.unicodeScalars[prev]) {
412+
break
413+
}
414+
loc = prev
402415
}
403416

404-
// #aabccccccdddddd
405-
// ^^- prev ^-requestedLoc
406-
// `- loc
407-
//
408-
// We offset the column by (requestedLoc - prev), which must be >=0 and on the same line.
409-
410-
let delta = requestedLoc.encodedOffset - prev.encodedOffset
417+
// ###aabccccccdddddd
418+
// ^ ^- loc ^-requestedLoc
419+
// `- startIndex
411420

412-
return Position(line: pos.line, utf16index: pos.utf16index - delta)
421+
let adjustedOffset = lineSlice.utf16.distance(from: startIndex, to: loc)
422+
return Position(line: pos.line, utf16index: adjustedOffset)
413423
}
414424

415425
func hover(_ req: Request<HoverRequest>) {

Tests/SourceKitTests/LocalSwiftTests.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,35 @@ final class LocalSwiftTests: XCTestCase {
220220
XCTAssertNotEqual(after, selfDot)
221221
}
222222

223+
func testCompletionPosition() {
224+
let url = URL(fileURLWithPath: "/a.swift")
225+
sk.allowUnexpectedNotification = true
226+
227+
sk.send(DidOpenTextDocument(textDocument: TextDocumentItem(
228+
url: url,
229+
language: .swift,
230+
version: 12,
231+
text: "foo")))
232+
233+
for col in 0 ... 3 {
234+
let inOrAfterFoo = try! sk.sendSync(CompletionRequest(
235+
textDocument: TextDocumentIdentifier(url),
236+
position: Position(line: 0, utf16index: col)))
237+
XCTAssertFalse(inOrAfterFoo.isIncomplete)
238+
XCTAssertFalse(inOrAfterFoo.items.isEmpty)
239+
}
240+
241+
let outOfRange1 = try! sk.sendSync(CompletionRequest(
242+
textDocument: TextDocumentIdentifier(url),
243+
position: Position(line: 0, utf16index: 4)))
244+
XCTAssertTrue(outOfRange1.isIncomplete)
245+
246+
let outOfRange2 = try! sk.sendSync(CompletionRequest(
247+
textDocument: TextDocumentIdentifier(url),
248+
position: Position(line: 1, utf16index: 0)))
249+
XCTAssertTrue(outOfRange2.isIncomplete)
250+
}
251+
223252
func testXMLToMarkdownDeclaration() {
224253
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
225254
<Declaration>func foo(_ bar: <Type usr="fake">Baz</Type>)</Declaration>

Tests/SourceKitTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extension LocalSwiftTests {
2929
// to regenerate.
3030
static let __allTests__LocalSwiftTests = [
3131
("testCompletion", testCompletion),
32+
("testCompletionPosition", testCompletionPosition),
3233
("testDocumentSymbolHighlight", testDocumentSymbolHighlight),
3334
("testEditing", testEditing),
3435
("testHover", testHover),

0 commit comments

Comments
 (0)