Skip to content

Commit cb6c995

Browse files
committed
Translate the semantic token legend used by clangd to the semantic token legend used by SourceKit-LSP
clangd uses a completely different semantic token legend than SourceKit-LSP (it doesn’t even adhere to the ordering of the pre-defined token types) but we were passing index offsets from clangd through assuming that clangd uses the same legend, which was incorrect. When retrieving semantic tokens from clangd, translate the semantic tokens from clangd’s legend to SourceKit-LSP’s legend. rdar://129895062
1 parent d1bf4fd commit cb6c995

File tree

11 files changed

+312
-36
lines changed

11 files changed

+312
-36
lines changed

Documentation/LSP Extensions.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,35 @@ Added field (this is an extension from clangd that SourceKit-LSP re-exposes):
3535
codeActions: CodeAction[]?
3636
```
3737
38+
## Semantic token modifiers
39+
40+
Added the following cases from clangd
41+
42+
```ts
43+
deduced = 'deduced'
44+
virtual = 'virtual'
45+
dependentName = 'dependentName'
46+
usedAsMutableReference = 'usedAsMutableReference'
47+
usedAsMutablePointer = 'usedAsMutablePointer'
48+
constructorOrDestructor = 'constructorOrDestructor'
49+
userDefined = 'userDefined'
50+
functionScope = 'functionScope'
51+
classScope = 'classScope'
52+
fileScope = 'fileScope'
53+
globalScope = 'globalScope'
54+
```
55+
3856
## Semantic token types
3957
58+
Added the following cases from clangd
59+
60+
```ts
61+
bracket = 'bracket'
62+
label = 'label'
63+
concept = 'concept'
64+
unknown = 'unknown'
65+
```
66+
4067
Added case
4168
4269
```ts

Sources/LanguageServerProtocol/SupportTypes/SemanticTokenModifiers.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ public struct SemanticTokenModifiers: OptionSet, Hashable, Sendable {
3434
public static let documentation = Self(rawValue: 1 << 8)
3535
public static let defaultLibrary = Self(rawValue: 1 << 9)
3636

37+
// The following are LSP extensions from clangd
38+
public static let deduced = Self(rawValue: 1 << 10)
39+
public static let virtual = Self(rawValue: 1 << 11)
40+
public static let dependentName = Self(rawValue: 1 << 12)
41+
public static let usedAsMutableReference = Self(rawValue: 1 << 13)
42+
public static let usedAsMutablePointer = Self(rawValue: 1 << 14)
43+
public static let constructorOrDestructor = Self(rawValue: 1 << 15)
44+
public static let userDefined = Self(rawValue: 1 << 16)
45+
public static let functionScope = Self(rawValue: 1 << 17)
46+
public static let classScope = Self(rawValue: 1 << 18)
47+
public static let fileScope = Self(rawValue: 1 << 19)
48+
public static let globalScope = Self(rawValue: 1 << 20)
49+
3750
public var name: String? {
3851
switch self {
3952
case .declaration: return "declaration"
@@ -46,13 +59,24 @@ public struct SemanticTokenModifiers: OptionSet, Hashable, Sendable {
4659
case .modification: return "modification"
4760
case .documentation: return "documentation"
4861
case .defaultLibrary: return "defaultLibrary"
62+
case .deduced: return "deduced"
63+
case .virtual: return "virtual"
64+
case .dependentName: return "dependentName"
65+
case .usedAsMutableReference: return "usedAsMutableReference"
66+
case .usedAsMutablePointer: return "usedAsMutablePointer"
67+
case .constructorOrDestructor: return "constructorOrDestructor"
68+
case .userDefined: return "userDefined"
69+
case .functionScope: return "functionScope"
70+
case .classScope: return "classScope"
71+
case .fileScope: return "fileScope"
72+
case .globalScope: return "globalScope"
4973
default: return nil
5074
}
5175
}
5276

5377
/// All available modifiers, in ascending order of the bit index
5478
/// they are represented with (starting at the rightmost bit).
55-
public static let predefined: [Self] = [
79+
public static let all: [Self] = [
5680
.declaration,
5781
.definition,
5882
.readonly,
@@ -63,5 +87,16 @@ public struct SemanticTokenModifiers: OptionSet, Hashable, Sendable {
6387
.modification,
6488
.documentation,
6589
.defaultLibrary,
90+
.deduced,
91+
.virtual,
92+
.dependentName,
93+
.usedAsMutableReference,
94+
.usedAsMutablePointer,
95+
.constructorOrDestructor,
96+
.userDefined,
97+
.functionScope,
98+
.classScope,
99+
.fileScope,
100+
.globalScope,
66101
]
67102
}

Sources/LanguageServerProtocol/SupportTypes/SemanticTokenTypes.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,18 @@ public struct SemanticTokenTypes: Hashable, Sendable {
5050
/// since 3.17.0
5151
public static let decorator = Self("decorator")
5252

53-
public static let predefined: [Self] = [
53+
// The following are LSP extensions from clangd
54+
public static let bracket = Self("bracket")
55+
public static let label = Self("label")
56+
public static let concept = Self("concept")
57+
public static let unknown = Self("unknown")
58+
59+
/// An identifier that hasn't been further classified
60+
///
61+
/// **(LSP Extension)**
62+
public static let identifier = Self("identifier")
63+
64+
public static let all: [Self] = [
5465
.namespace,
5566
.type,
5667
.class,
@@ -73,5 +84,11 @@ public struct SemanticTokenTypes: Hashable, Sendable {
7384
.number,
7485
.regexp,
7586
.operator,
87+
.decorator,
88+
.bracket,
89+
.label,
90+
.concept,
91+
.unknown,
92+
.identifier,
7693
]
7794
}

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_library(SourceKitLSP STATIC
1111
MessageHandlingDependencyTracker.swift
1212
Rename.swift
1313
ResponseError+Init.swift
14+
SemanticTokensLegend+SourceKitLSPLegend.swift
1415
SourceKitIndexDelegate.swift
1516
SourceKitLSPCommandMetadata.swift
1617
SourceKitLSPServer.swift
@@ -22,7 +23,9 @@ add_library(SourceKitLSP STATIC
2223
Workspace.swift
2324
)
2425
target_sources(SourceKitLSP PRIVATE
25-
Clang/ClangLanguageService.swift)
26+
Clang/ClangLanguageService.swift
27+
Clang/SemanticTokenTranslator.swift
28+
)
2629
target_sources(SourceKitLSP PRIVATE
2730
Swift/AdjustPositionToStartOfIdentifier.swift
2831
Swift/CodeActions/AddDocumentation.swift

Sources/SourceKitLSP/Clang/ClangLanguageService.swift

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ actor ClangLanguageService: LanguageService, MessageHandler {
9595
/// opened with.
9696
private var openDocuments: [DocumentURI: Language] = [:]
9797

98+
/// Type to map `clangd`'s semantic token legend to SourceKit-LSP's.
99+
private var semanticTokensTranslator: SemanticTokensLegendTranslator? = nil
100+
98101
/// While `clangd` is running, its PID.
99102
#if os(Windows)
100103
private var hClangd: HANDLE = INVALID_HANDLE_VALUE
@@ -412,6 +415,12 @@ extension ClangLanguageService {
412415

413416
let result = try await clangd.send(initialize)
414417
self.capabilities = result.capabilities
418+
if let legend = result.capabilities.semanticTokensProvider?.legend {
419+
self.semanticTokensTranslator = SemanticTokensLegendTranslator(
420+
clangdLegend: legend,
421+
sourceKitLSPLegend: SemanticTokensLegend.sourceKitLSPLegend
422+
)
423+
}
415424
return result
416425
}
417426

@@ -537,19 +546,50 @@ extension ClangLanguageService {
537546
}
538547

539548
func documentSemanticTokens(_ req: DocumentSemanticTokensRequest) async throws -> DocumentSemanticTokensResponse? {
540-
return try await forwardRequestToClangd(req)
549+
guard var response = try await forwardRequestToClangd(req) else {
550+
return nil
551+
}
552+
if let semanticTokensTranslator {
553+
response.data = semanticTokensTranslator.translate(response.data)
554+
}
555+
return response
541556
}
542557

543558
func documentSemanticTokensDelta(
544559
_ req: DocumentSemanticTokensDeltaRequest
545560
) async throws -> DocumentSemanticTokensDeltaResponse? {
546-
return try await forwardRequestToClangd(req)
561+
guard var response = try await forwardRequestToClangd(req) else {
562+
return nil
563+
}
564+
if let semanticTokensTranslator {
565+
switch response {
566+
case .tokens(var tokens):
567+
tokens.data = semanticTokensTranslator.translate(tokens.data)
568+
response = .tokens(tokens)
569+
case .delta(var delta):
570+
delta.edits = delta.edits.map {
571+
var edit = $0
572+
if let data = edit.data {
573+
edit.data = semanticTokensTranslator.translate(data)
574+
}
575+
return edit
576+
}
577+
response = .delta(delta)
578+
}
579+
}
580+
return response
547581
}
548582

549583
func documentSemanticTokensRange(
550584
_ req: DocumentSemanticTokensRangeRequest
551585
) async throws -> DocumentSemanticTokensResponse? {
552-
return try await forwardRequestToClangd(req)
586+
guard var response = try await forwardRequestToClangd(req) else {
587+
return nil
588+
}
589+
if let semanticTokensTranslator {
590+
response.data = semanticTokensTranslator.translate(response.data)
591+
}
592+
return response
553593
}
554594

555595
func colorPresentation(_ req: ColorPresentationRequest) async throws -> [ColorPresentation] {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LSPLogging
14+
import LanguageServerProtocol
15+
16+
/// `clangd` might use a different semantic token legend than SourceKit-LSP.
17+
///
18+
/// This type allows translation the semantic tokens from `clangd` into the token legend that is used by SourceKit-LSP.
19+
struct SemanticTokensLegendTranslator {
20+
private enum Translation {
21+
/// The token type or modifier from clangd does not exist in SourceKit-LSP
22+
case doesNotExistInSourceKitLSP
23+
24+
/// The token type or modifier exists in SourceKit-LSP but it uses a different index. We need to translate the
25+
/// clangd index to this SourceKit-LSP index.
26+
case translation(UInt32)
27+
}
28+
29+
/// For all token types whose representation in clang differs from the representation in SourceKit-LSP, maps the
30+
/// index of that token type in clangd’s token type legend to the corresponding representation in SourceKit-LSP.
31+
private let tokenTypeTranslations: [UInt32: Translation]
32+
33+
/// For all token modifiers whose representation in clang differs from the representation in SourceKit-LSP, maps the
34+
/// index of that token type in clangd’s token type legend to the corresponding representation in SourceKit-LSP.
35+
private let tokenModifierTranslations: [UInt32: Translation]
36+
37+
/// A bitmask that has all bits set to 1 that are used for clangd token modifiers which have a different
38+
/// representation in SourceKit-LSP. If a token modifier does not have any bits set in common with this bitmask, no
39+
/// token mapping needs to be performed.
40+
private let tokenModifierTranslationBitmask: UInt32
41+
42+
/// For token types in clangd that do not exist in SourceKit-LSP's token legend, we need to map their token types to
43+
/// some valid SourceKit-LSP token type. Use the token type with this index.
44+
private let tokenTypeFallbackIndex: UInt32
45+
46+
init(clangdLegend: SemanticTokensLegend, sourceKitLSPLegend: SemanticTokensLegend) {
47+
var tokenTypeTranslations: [UInt32: Translation] = [:]
48+
for (index, tokenType) in clangdLegend.tokenTypes.enumerated() {
49+
switch sourceKitLSPLegend.tokenTypes.firstIndex(of: tokenType) {
50+
case index:
51+
break
52+
case nil:
53+
logger.error("Token type '\(tokenType, privacy: .public)' from clangd does not exist in SourceKit-LSP's legend")
54+
tokenTypeTranslations[UInt32(index)] = .doesNotExistInSourceKitLSP
55+
case let sourceKitLSPIndex?:
56+
logger.info(
57+
"Token type '\(tokenType, privacy: .public)' from clangd at index \(index) translated to \(sourceKitLSPIndex)"
58+
)
59+
tokenTypeTranslations[UInt32(index)] = .translation(UInt32(sourceKitLSPIndex))
60+
}
61+
}
62+
self.tokenTypeTranslations = tokenTypeTranslations
63+
64+
var tokenModifierTranslations: [UInt32: Translation] = [:]
65+
for (index, tokenModifier) in clangdLegend.tokenModifiers.enumerated() {
66+
switch sourceKitLSPLegend.tokenModifiers.firstIndex(of: tokenModifier) {
67+
case index:
68+
break
69+
case nil:
70+
logger.error(
71+
"Token modifier '\(tokenModifier, privacy: .public)' from clangd does not exist in SourceKit-LSP's legend"
72+
)
73+
tokenModifierTranslations[UInt32(index)] = .doesNotExistInSourceKitLSP
74+
case let sourceKitLSPIndex?:
75+
logger.error(
76+
"Token modifier '\(tokenModifier, privacy: .public)' from clangd at index \(index) translated to \(sourceKitLSPIndex)"
77+
)
78+
tokenModifierTranslations[UInt32(index)] = .translation(UInt32(sourceKitLSPIndex))
79+
}
80+
}
81+
self.tokenModifierTranslations = tokenModifierTranslations
82+
83+
var tokenModifierTranslationBitmask: UInt32 = 0
84+
for translatedIndex in tokenModifierTranslations.keys {
85+
tokenModifierTranslationBitmask.setBitToOne(at: Int(translatedIndex))
86+
}
87+
self.tokenModifierTranslationBitmask = tokenModifierTranslationBitmask
88+
89+
self.tokenTypeFallbackIndex = UInt32(
90+
sourceKitLSPLegend.tokenTypes.firstIndex(of: SemanticTokenTypes.unknown.name) ?? 0
91+
)
92+
}
93+
94+
func translate(_ data: [UInt32]) -> [UInt32] {
95+
var data = data
96+
// Translate token types, which are at offset n + 3.
97+
for i in stride(from: 3, to: data.count, by: 5) {
98+
switch tokenTypeTranslations[data[i]] {
99+
case .doesNotExistInSourceKitLSP: data[i] = tokenTypeFallbackIndex
100+
case .translation(let translatedIndex): data[i] = translatedIndex
101+
case nil: break
102+
}
103+
}
104+
105+
// Translate token modifiers, which are at offset n + 4
106+
for i in stride(from: 4, to: data.count, by: 5) {
107+
guard data[i] & tokenModifierTranslationBitmask != 0 else {
108+
// Fast path: There is nothing to translate
109+
continue
110+
}
111+
var translatedModifiersBitmask: UInt32 = 0
112+
for (clangdModifier, sourceKitLSPModifier) in tokenModifierTranslations {
113+
guard data[i].hasBitSet(at: Int(clangdModifier)) else {
114+
continue
115+
}
116+
switch sourceKitLSPModifier {
117+
case .doesNotExistInSourceKitLSP: break
118+
case .translation(let sourceKitLSPIndex): translatedModifiersBitmask.setBitToOne(at: Int(sourceKitLSPIndex))
119+
}
120+
}
121+
data[i] = data[i] & ~tokenModifierTranslationBitmask | translatedModifiersBitmask
122+
}
123+
124+
return data
125+
}
126+
}
127+
128+
fileprivate extension UInt32 {
129+
mutating func hasBitSet(at index: Int) -> Bool {
130+
return self & (1 << index) != 0
131+
}
132+
133+
mutating func setBitToOne(at index: Int) {
134+
self |= 1 << index
135+
}
136+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LanguageServerProtocol
14+
15+
extension SemanticTokenTypes {
16+
// LSP doesn’t know about actors. Display actors as classes.
17+
public static var actor: Self { Self.class }
18+
19+
/// Token types are looked up by index
20+
public var tokenType: UInt32 {
21+
UInt32(Self.all.firstIndex(of: self)!)
22+
}
23+
}
24+
25+
extension SemanticTokensLegend {
26+
/// The semantic tokens legend that is used between SourceKit-LSP and the editor.
27+
static let sourceKitLSPLegend = SemanticTokensLegend(
28+
tokenTypes: SemanticTokenTypes.all.map(\.name),
29+
tokenModifiers: SemanticTokenModifiers.all.compactMap(\.name)
30+
)
31+
}

0 commit comments

Comments
 (0)