Skip to content

Commit af150a3

Browse files
committed
Don’t offer text edits from inlay hints if they are syntactically invalid
Don’t offer any text edits for inlay hints displayed in closure paramters or enum case items. Fixes #759 rdar://111559715
1 parent 644214a commit af150a3

File tree

3 files changed

+52
-9
lines changed

3 files changed

+52
-9
lines changed

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,13 +1320,17 @@ extension SwiftLanguageServer {
13201320
.map { info -> InlayHint in
13211321
let position = info.range.upperBound
13221322
let label = ": \(info.printedType)"
1323+
let textEdits: [TextEdit]?
1324+
if info.canBeFollowedByTypeAnnotation {
1325+
textEdits = [TextEdit(range: position..<position, newText: label)]
1326+
} else {
1327+
textEdits = nil
1328+
}
13231329
return InlayHint(
13241330
position: position,
13251331
label: .string(label),
13261332
kind: .type,
1327-
textEdits: [
1328-
TextEdit(range: position..<position, newText: label)
1329-
]
1333+
textEdits: textEdits
13301334
)
13311335
}
13321336

Sources/SourceKitLSP/Swift/VariableTypeInfo.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,35 @@
1313
import Dispatch
1414
import LanguageServerProtocol
1515
import SourceKitD
16+
import SwiftSyntax
17+
18+
fileprivate extension TokenSyntax {
19+
/// Returns `false` if it is known that this token can’t be followed by a type
20+
/// annotation.
21+
var canBeFollowedByTypeAnnotation: Bool {
22+
var node = Syntax(self)
23+
LOOP: while let parent = node.parent {
24+
switch parent.kind {
25+
case .caseItem, .closureParam:
26+
// case items (inside a switch) and closure parameters can’t have type
27+
// annotations.
28+
return false
29+
case .codeBlockItem, .memberDeclListItem:
30+
// Performanc optimization. If we walked the parents up to code block item,
31+
// we can’t enter a case item or closure param anymore. No need walking
32+
// the tree any further.
33+
break LOOP
34+
default:
35+
break
36+
}
37+
node = parent
38+
}
39+
40+
// By default, assume that the token can be followed by a type annotation as
41+
// most locations that produce a variable type info can.
42+
return true
43+
}
44+
}
1645

1746
/// A typed variable as returned by sourcekitd's CollectVariableType.
1847
struct VariableTypeInfo {
@@ -22,6 +51,9 @@ struct VariableTypeInfo {
2251
var printedType: String
2352
/// Whether the variable has an explicit type annotation in the source file.
2453
var hasExplicitType: Bool
54+
/// Whether we should suggest making an edit to add the type annotation to the
55+
/// source file.
56+
var canBeFollowedByTypeAnnotation: Bool
2557

2658
init?(_ dict: SKDResponseDictionary, in snapshot: DocumentSnapshot) {
2759
let keys = dict.sourcekitd.keys
@@ -34,10 +66,12 @@ struct VariableTypeInfo {
3466
let hasExplicitType: Bool = dict[keys.variable_type_explicit] else {
3567
return nil
3668
}
69+
let tokenAtOffset = snapshot.tokens.syntaxTree?.token(at: AbsolutePosition(utf8Offset: offset))
3770

3871
self.range = startIndex..<endIndex
3972
self.printedType = printedType
4073
self.hasExplicitType = hasExplicitType
74+
self.canBeFollowedByTypeAnnotation = tokenAtOffset?.canBeFollowedByTypeAnnotation ?? true
4175
}
4276
}
4377

Tests/SourceKitLSPTests/InlayHintTests.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,18 @@ final class InlayHintTests: XCTestCase {
6161
}
6262
}
6363

64-
private func makeInlayHint(position: Position, kind: InlayHintKind, label: String) -> InlayHint {
65-
InlayHint(
64+
private func makeInlayHint(position: Position, kind: InlayHintKind, label: String, hasEdit: Bool = true) -> InlayHint {
65+
let textEdits: [TextEdit]?
66+
if hasEdit {
67+
textEdits = [TextEdit(range: position..<position, newText: label)]
68+
} else {
69+
textEdits = nil
70+
}
71+
return InlayHint(
6672
position: position,
6773
label: .string(label),
6874
kind: kind,
69-
textEdits: [
70-
TextEdit(range: position..<position, newText: label)
71-
]
75+
textEdits: textEdits
7276
)
7377
}
7478

@@ -205,7 +209,8 @@ final class InlayHintTests: XCTestCase {
205209
makeInlayHint(
206210
position: Position(line: 3, utf16index: 31),
207211
kind: .type,
208-
label: ": String"
212+
label: ": String",
213+
hasEdit: false
209214
),
210215
makeInlayHint(
211216
position: Position(line: 4, utf16index: 40),

0 commit comments

Comments
 (0)