Skip to content

Commit 9ca8966

Browse files
committed
Swiftinterface symbol lookup
Generate swiftinterface for symbol lookup When a symbol definition returns it is in a swiftinterface file, create textual version of swiftinterface and return that in response. Extend OpenInterface to also seatch for a symbol Fix warning Syntax changes after review Move module name split into OpenInterfaceRequest Use group names when running open interface request Requested changes from PR rename symbol to symbolUSR Cleanup OpenInterfaceRequest.init Fix tests Added testDefinitionInSystemModuleInterface Use SwiftPMPackage test module Added version of buildAndIndex that includes system symbols Merge buildAndIndexWithSystemSymbols with buildAndIndex Added specific test project for system swiftinterface tests Add multiple tests for various system modules
1 parent 2930ac3 commit 9ca8966

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
@@ -1292,20 +1292,7 @@ extension SourceKitServer {
12921292

12931293
// If this symbol is a module then generate a textual interface
12941294
if case .success(let symbols) = result, let symbol = symbols.first, symbol.kind == .module, let name = symbol.name {
1295-
let openInterface = OpenInterfaceRequest(textDocument: req.params.textDocument, name: name)
1296-
let request = Request(openInterface, id: req.id, clientID: ObjectIdentifier(self),
1297-
cancellation: req.cancellationToken, reply: { (result: Result<OpenInterfaceRequest.Response, ResponseError>) in
1298-
switch result {
1299-
case .success(let interfaceDetails?):
1300-
let loc = Location(uri: interfaceDetails.uri, range: Range(Position(line: 0, utf16index: 0)))
1301-
req.reply(.locations([loc]))
1302-
case .success(nil):
1303-
req.reply(.failure(.unknown("Could not generate Swift Interface for \(name)")))
1304-
case .failure(let error):
1305-
req.reply(.failure(error))
1306-
}
1307-
})
1308-
languageService.openInterface(request)
1295+
self.respondWithInterface(req, moduleName: name, symbolUSR: nil, languageService: languageService)
13091296
return
13101297
}
13111298

@@ -1320,6 +1307,19 @@ extension SourceKitServer {
13201307

13211308
switch extractedResult {
13221309
case .success(let resolved):
1310+
// if first resolved location is in `.swiftinterface` file. Use moduleName to return
1311+
// textual interface
1312+
if let firstResolved = resolved.first,
1313+
let moduleName = firstResolved.occurrence?.location.moduleName,
1314+
firstResolved.location.uri.fileURL?.pathExtension == "swiftinterface" {
1315+
self.respondWithInterface(
1316+
req,
1317+
moduleName: moduleName,
1318+
symbolUSR: firstResolved.occurrence?.symbol.usr,
1319+
languageService: languageService
1320+
)
1321+
return
1322+
}
13231323
let locs = resolved.map(\.location)
13241324
// If we're unable to handle the definition request using our index, see if the
13251325
// language service can handle it (e.g. clangd can provide AST based definitions).
@@ -1339,6 +1339,29 @@ extension SourceKitServer {
13391339
languageService.symbolInfo(request)
13401340
}
13411341

1342+
func respondWithInterface(
1343+
_ req: Request<DefinitionRequest>,
1344+
moduleName: String,
1345+
symbolUSR: String?,
1346+
languageService: ToolchainLanguageServer
1347+
) {
1348+
let openInterface = OpenInterfaceRequest(textDocument: req.params.textDocument, name: moduleName, symbolUSR: symbolUSR)
1349+
let request = Request(openInterface, id: req.id, clientID: ObjectIdentifier(self),
1350+
cancellation: req.cancellationToken, reply: { (result: Result<OpenInterfaceRequest.Response, ResponseError>) in
1351+
switch result {
1352+
case .success(let interfaceDetails?):
1353+
let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0)
1354+
let loc = Location(uri: interfaceDetails.uri, range: Range(position))
1355+
req.reply(.locations([loc]))
1356+
case .success(nil):
1357+
req.reply(.failure(.unknown("Could not generate Swift Interface for \(moduleName)")))
1358+
case .failure(let error):
1359+
req.reply(.failure(error))
1360+
}
1361+
})
1362+
languageService.openInterface(request)
1363+
}
1364+
13421365
func implementation(
13431366
_ req: Request<ImplementationRequest>,
13441367
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)