Skip to content

Commit ff59c1a

Browse files
authored
Merge pull request #747 from adam-fowler/af-swiftinterface-symbol-lookup
Swiftinterface symbol lookup
2 parents 7dafe17 + 07de4c4 commit ff59c1a

File tree

8 files changed

+248
-39
lines changed

8 files changed

+248
-39
lines changed

Sources/LanguageServerProtocol/Requests/OpenInterfaceRequest.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,41 @@ public struct OpenInterfaceRequest: TextDocumentRequest, Hashable {
2020
public var textDocument: TextDocumentIdentifier
2121

2222
/// The module to generate an index for.
23-
public var name: String
23+
public var moduleName: String
2424

25-
public init(textDocument: TextDocumentIdentifier, name: String) {
25+
/// The module group name.
26+
public var groupNames: [String]
27+
28+
/// The symbol USR to search for in the generated module interface.
29+
public var symbolUSR: String?
30+
31+
public init(textDocument: TextDocumentIdentifier, name: String, symbolUSR: String?) {
2632
self.textDocument = textDocument
27-
self.name = name
33+
self.symbolUSR = symbolUSR
34+
// Stdlib Swift modules are all in the "Swift" module, but their symbols return a module name `Swift.***`.
35+
let splitName = name.split(separator: ".")
36+
self.moduleName = String(splitName[0])
37+
self.groupNames = [String.SubSequence](splitName.dropFirst()).map(String.init)
38+
}
39+
40+
/// Name of interface module name with group names appended
41+
public var name: String {
42+
if groupNames.count > 0 {
43+
return "\(self.moduleName).\(self.groupNames.joined(separator: "."))"
44+
} else {
45+
return self.moduleName
46+
}
2847
}
2948
}
3049

3150
/// The textual output of a module interface.
3251
public struct InterfaceDetails: ResponseType, Hashable {
3352

3453
public var uri: DocumentURI
54+
public var position: Position?
3555

36-
public init(uri: DocumentURI) {
56+
public init(uri: DocumentURI, position: Position?) {
3757
self.uri = uri
58+
self.position = position
3859
}
3960
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// swift-tools-version:5.1
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "SystemSwiftInterface",
7+
platforms: [.macOS(.v10_15)],
8+
products: [],
9+
dependencies: [],
10+
targets: [
11+
.target(
12+
name: "lib",
13+
dependencies: []),
14+
/*Package.swift:targets*/
15+
]
16+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public func libFunc() async {
2+
let a: /*lib.string*/String = "test"
3+
let i: /*lib.integer*/Int = 2
4+
await /*lib.withTaskGroup*/withTaskGroup(of: Void.self) { group in
5+
group.addTask {
6+
print(a)
7+
print(i)
8+
}
9+
}
10+
}

Sources/SKTestSupport/SKSwiftPMTestWorkspace.swift

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,20 +120,25 @@ extension SKSwiftPMTestWorkspace {
120120

121121
public func testLoc(_ name: String) -> TestLocation { sources.locations[name]! }
122122

123-
public func buildAndIndex() throws {
124-
try build()
123+
public func buildAndIndex(withSystemSymbols: Bool = false) throws {
124+
try build(withSystemSymbols: withSystemSymbols)
125125
index.pollForUnitChangesAndWait()
126126
}
127127

128-
func build() throws {
129-
try TSCBasic.Process.checkNonZeroExit(arguments: [
128+
func build(withSystemSymbols: Bool = false) throws {
129+
var arguments = [
130130
toolchain.swift!.pathString,
131131
"build",
132132
"--package-path", sources.rootDirectory.path,
133133
"--scratch-path", buildDir.path,
134-
"-Xswiftc", "-index-ignore-system-modules",
135-
"-Xcc", "-index-ignore-system-symbols",
136-
])
134+
]
135+
if !withSystemSymbols {
136+
arguments.append(contentsOf: [
137+
"-Xswiftc", "-index-ignore-system-modules",
138+
"-Xcc", "-index-ignore-system-symbols",
139+
])
140+
}
141+
try TSCBasic.Process.checkNonZeroExit(arguments: arguments)
137142
}
138143
}
139144

Sources/SourceKitD/sourcekitd_uids.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public struct sourcekitd_keys {
4040
public let expression_type_list: sourcekitd_uid_t
4141
public let filepath: sourcekitd_uid_t
4242
public let fixits: sourcekitd_uid_t
43+
public let groupname: sourcekitd_uid_t
4344
public let id: sourcekitd_uid_t
4445
public let is_system: sourcekitd_uid_t
4546
public let kind: sourcekitd_uid_t
@@ -115,6 +116,7 @@ public struct sourcekitd_keys {
115116
expression_type_list = api.uid_get_from_cstr("key.expression_type_list")!
116117
filepath = api.uid_get_from_cstr("key.filepath")!
117118
fixits = api.uid_get_from_cstr("key.fixits")!
119+
groupname = api.uid_get_from_cstr("key.groupname")!
118120
id = api.uid_get_from_cstr("key.id")!
119121
is_system = api.uid_get_from_cstr("key.is_system")!
120122
kind = api.uid_get_from_cstr("key.kind")!
@@ -176,6 +178,7 @@ public struct sourcekitd_requests {
176178
public let codecomplete_close: sourcekitd_uid_t
177179
public let cursorinfo: sourcekitd_uid_t
178180
public let expression_type: sourcekitd_uid_t
181+
public let find_usr: sourcekitd_uid_t
179182
public let variable_type: sourcekitd_uid_t
180183
public let relatedidents: sourcekitd_uid_t
181184
public let semantic_refactoring: sourcekitd_uid_t
@@ -192,6 +195,7 @@ public struct sourcekitd_requests {
192195
codecomplete_close = api.uid_get_from_cstr("source.request.codecomplete.close")!
193196
cursorinfo = api.uid_get_from_cstr("source.request.cursorinfo")!
194197
expression_type = api.uid_get_from_cstr("source.request.expression.type")!
198+
find_usr = api.uid_get_from_cstr("source.request.editor.find_usr")!
195199
variable_type = api.uid_get_from_cstr("source.request.variable.type")!
196200
relatedidents = api.uid_get_from_cstr("source.request.relatedidents")!
197201
semantic_refactoring = api.uid_get_from_cstr("source.request.semantic.refactoring")!

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,20 +1307,7 @@ extension SourceKitServer {
13071307

13081308
// If this symbol is a module then generate a textual interface
13091309
if case .success(let symbols) = result, let symbol = symbols.first, symbol.kind == .module, let name = symbol.name {
1310-
let openInterface = OpenInterfaceRequest(textDocument: req.params.textDocument, name: name)
1311-
let request = Request(openInterface, id: req.id, clientID: ObjectIdentifier(self),
1312-
cancellation: req.cancellationToken, reply: { (result: Result<OpenInterfaceRequest.Response, ResponseError>) in
1313-
switch result {
1314-
case .success(let interfaceDetails?):
1315-
let loc = Location(uri: interfaceDetails.uri, range: Range(Position(line: 0, utf16index: 0)))
1316-
req.reply(.locations([loc]))
1317-
case .success(nil):
1318-
req.reply(.failure(.unknown("Could not generate Swift Interface for \(name)")))
1319-
case .failure(let error):
1320-
req.reply(.failure(error))
1321-
}
1322-
})
1323-
languageService.openInterface(request)
1310+
self.respondWithInterface(req, moduleName: name, symbolUSR: nil, languageService: languageService)
13241311
return
13251312
}
13261313

@@ -1335,6 +1322,19 @@ extension SourceKitServer {
13351322

13361323
switch extractedResult {
13371324
case .success(let resolved):
1325+
// if first resolved location is in `.swiftinterface` file. Use moduleName to return
1326+
// textual interface
1327+
if let firstResolved = resolved.first,
1328+
let moduleName = firstResolved.occurrence?.location.moduleName,
1329+
firstResolved.location.uri.fileURL?.pathExtension == "swiftinterface" {
1330+
self.respondWithInterface(
1331+
req,
1332+
moduleName: moduleName,
1333+
symbolUSR: firstResolved.occurrence?.symbol.usr,
1334+
languageService: languageService
1335+
)
1336+
return
1337+
}
13381338
let locs = resolved.map(\.location)
13391339
// If we're unable to handle the definition request using our index, see if the
13401340
// language service can handle it (e.g. clangd can provide AST based definitions).
@@ -1354,6 +1354,29 @@ extension SourceKitServer {
13541354
languageService.symbolInfo(request)
13551355
}
13561356

1357+
func respondWithInterface(
1358+
_ req: Request<DefinitionRequest>,
1359+
moduleName: String,
1360+
symbolUSR: String?,
1361+
languageService: ToolchainLanguageServer
1362+
) {
1363+
let openInterface = OpenInterfaceRequest(textDocument: req.params.textDocument, name: moduleName, symbolUSR: symbolUSR)
1364+
let request = Request(openInterface, id: req.id, clientID: ObjectIdentifier(self),
1365+
cancellation: req.cancellationToken, reply: { (result: Result<OpenInterfaceRequest.Response, ResponseError>) in
1366+
switch result {
1367+
case .success(let interfaceDetails?):
1368+
let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0)
1369+
let loc = Location(uri: interfaceDetails.uri, range: Range(position))
1370+
req.reply(.locations([loc]))
1371+
case .success(nil):
1372+
req.reply(.failure(.unknown("Could not generate Swift Interface for \(moduleName)")))
1373+
case .failure(let error):
1374+
req.reply(.failure(error))
1375+
}
1376+
})
1377+
languageService.openInterface(request)
1378+
}
1379+
13571380
func implementation(
13581381
_ req: Request<ImplementationRequest>,
13591382
workspace: Workspace,

Sources/SourceKitLSP/Swift/OpenInterface.swift

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,46 @@ import Foundation
1414
import SourceKitD
1515
import LanguageServerProtocol
1616
import LSPLogging
17+
import SKSupport
1718

1819
struct InterfaceInfo {
1920
var contents: String
2021
}
2122

23+
struct FindUSRInfo {
24+
let position: Position?
25+
}
26+
2227
extension SwiftLanguageServer {
2328
public func openInterface(_ request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>) {
2429
let uri = request.params.textDocument.uri
25-
let moduleName = request.params.name
30+
let moduleName = request.params.moduleName
31+
let name = request.params.name
32+
let symbol = request.params.symbolUSR
2633
self.queue.async {
27-
let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(moduleName).swiftinterface")
34+
let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(name).swiftinterface")
2835
let interfaceDocURI = DocumentURI(interfaceFilePath)
29-
self._openInterface(request: request, uri: uri, name: moduleName, interfaceURI: interfaceDocURI) { result in
30-
switch result {
31-
case .success(let interfaceInfo):
32-
do {
33-
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
34-
request.reply(.success(InterfaceDetails(uri: interfaceDocURI)))
35-
} catch {
36-
request.reply(.failure(ResponseError.unknown(error.localizedDescription)))
36+
// has interface already been generated
37+
if let snapshot = self.documentManager.latestSnapshot(interfaceDocURI) {
38+
self._findUSRAndRespond(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol)
39+
} else {
40+
// generate interface
41+
self._openInterface(request: request, uri: uri, name: moduleName, interfaceURI: interfaceDocURI) { result in
42+
switch result {
43+
case .success(let interfaceInfo):
44+
do {
45+
// write to file
46+
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
47+
// store snapshot
48+
let snapshot = try self.documentManager.open(interfaceDocURI, language: .swift, version: 0, text: interfaceInfo.contents)
49+
self._findUSRAndRespond(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol)
50+
} catch {
51+
request.reply(.failure(ResponseError.unknown(error.localizedDescription)))
52+
}
53+
case .failure(let error):
54+
log("open interface failed: \(error)", level: .warning)
55+
request.reply(.failure(ResponseError(error)))
3756
}
38-
case .failure(let error):
39-
log("open interface failed: \(error)", level: .warning)
40-
request.reply(.failure(ResponseError(error)))
4157
}
4258
}
4359
}
@@ -60,6 +76,9 @@ extension SwiftLanguageServer {
6076
let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)
6177
skreq[keys.request] = requests.editor_open_interface
6278
skreq[keys.modulename] = name
79+
if request.params.groupNames.count > 0 {
80+
skreq[keys.groupname] = request.params.groupNames
81+
}
6382
skreq[keys.name] = interfaceURI.pseudoPath
6483
skreq[keys.synthesizedextensions] = 1
6584
if let compileCommand = self.commandsByFile[uri] {
@@ -81,4 +100,58 @@ extension SwiftLanguageServer {
81100
}
82101
}
83102
}
103+
104+
private func _findUSRAndRespond(
105+
request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>,
106+
uri: DocumentURI,
107+
snapshot: DocumentSnapshot,
108+
symbol: String?
109+
) {
110+
self._findUSR(request: request, uri: uri, snapshot: snapshot, symbol: symbol) { result in
111+
switch result {
112+
case .success(let info):
113+
request.reply(.success(InterfaceDetails(uri: uri, position: info.position)))
114+
case .failure:
115+
request.reply(.success(InterfaceDetails(uri: uri, position: nil)))
116+
}
117+
}
118+
}
119+
120+
private func _findUSR(
121+
request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>,
122+
uri: DocumentURI,
123+
snapshot: DocumentSnapshot,
124+
symbol: String?,
125+
completion: @escaping (Swift.Result<FindUSRInfo, SKDError>) -> Void
126+
) {
127+
guard let symbol = symbol else {
128+
return completion(.success(FindUSRInfo(position: nil)))
129+
}
130+
let keys = self.keys
131+
let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)
132+
133+
skreq[keys.request] = requests.find_usr
134+
skreq[keys.sourcefile] = uri.pseudoPath
135+
skreq[keys.usr] = symbol
136+
137+
let handle = self.sourcekitd.send(skreq, self.queue) { result in
138+
switch result {
139+
case .success(let dict):
140+
if let offset: Int = dict[keys.offset],
141+
let position = snapshot.positionOf(utf8Offset: offset) {
142+
return completion(.success(FindUSRInfo(position: position)))
143+
} else {
144+
return completion(.success(FindUSRInfo(position: nil)))
145+
}
146+
case .failure(let error):
147+
return completion(.failure(error))
148+
}
149+
}
150+
151+
if let handle = handle {
152+
request.cancellationToken.addCancellationHandler { [weak self] in
153+
self?.sourcekitd.cancel(handle)
154+
}
155+
}
156+
}
84157
}

0 commit comments

Comments
 (0)