Skip to content

Commit

Permalink
Merge pull request #750 from adam-fowler/af-swiftinterface-symbol-loo…
Browse files Browse the repository at this point in the history
…kup-5.9

Swiftinterface symbol lookup (release/5.9)
  • Loading branch information
ahoppen authored May 26, 2023
2 parents 2930ac3 + 9ca8966 commit ff667c2
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 39 deletions.
29 changes: 25 additions & 4 deletions Sources/LanguageServerProtocol/Requests/OpenInterfaceRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,41 @@ public struct OpenInterfaceRequest: TextDocumentRequest, Hashable {
public var textDocument: TextDocumentIdentifier

/// The module to generate an index for.
public var name: String
public var moduleName: String

public init(textDocument: TextDocumentIdentifier, name: String) {
/// The module group name.
public var groupNames: [String]

/// The symbol USR to search for in the generated module interface.
public var symbolUSR: String?

public init(textDocument: TextDocumentIdentifier, name: String, symbolUSR: String?) {
self.textDocument = textDocument
self.name = name
self.symbolUSR = symbolUSR
// Stdlib Swift modules are all in the "Swift" module, but their symbols return a module name `Swift.***`.
let splitName = name.split(separator: ".")
self.moduleName = String(splitName[0])
self.groupNames = [String.SubSequence](splitName.dropFirst()).map(String.init)
}

/// Name of interface module name with group names appended
public var name: String {
if groupNames.count > 0 {
return "\(self.moduleName).\(self.groupNames.joined(separator: "."))"
} else {
return self.moduleName
}
}
}

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

public var uri: DocumentURI
public var position: Position?

public init(uri: DocumentURI) {
public init(uri: DocumentURI, position: Position?) {
self.uri = uri
self.position = position
}
}
16 changes: 16 additions & 0 deletions Sources/SKTestSupport/INPUTS/SystemSwiftInterface/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// swift-tools-version:5.1

import PackageDescription

let package = Package(
name: "SystemSwiftInterface",
platforms: [.macOS(.v10_15)],
products: [],
dependencies: [],
targets: [
.target(
name: "lib",
dependencies: []),
/*Package.swift:targets*/
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public func libFunc() async {
let a: /*lib.string*/String = "test"
let i: /*lib.integer*/Int = 2
await /*lib.withTaskGroup*/withTaskGroup(of: Void.self) { group in
group.addTask {
print(a)
print(i)
}
}
}
19 changes: 12 additions & 7 deletions Sources/SKTestSupport/SKSwiftPMTestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,25 @@ extension SKSwiftPMTestWorkspace {

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

public func buildAndIndex() throws {
try build()
public func buildAndIndex(withSystemSymbols: Bool = false) throws {
try build(withSystemSymbols: withSystemSymbols)
index.pollForUnitChangesAndWait()
}

func build() throws {
try TSCBasic.Process.checkNonZeroExit(arguments: [
func build(withSystemSymbols: Bool = false) throws {
var arguments = [
toolchain.swift!.pathString,
"build",
"--package-path", sources.rootDirectory.path,
"--scratch-path", buildDir.path,
"-Xswiftc", "-index-ignore-system-modules",
"-Xcc", "-index-ignore-system-symbols",
])
]
if !withSystemSymbols {
arguments.append(contentsOf: [
"-Xswiftc", "-index-ignore-system-modules",
"-Xcc", "-index-ignore-system-symbols",
])
}
try TSCBasic.Process.checkNonZeroExit(arguments: arguments)
}
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/SourceKitD/sourcekitd_uids.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public struct sourcekitd_keys {
public let expression_type_list: sourcekitd_uid_t
public let filepath: sourcekitd_uid_t
public let fixits: sourcekitd_uid_t
public let groupname: sourcekitd_uid_t
public let id: sourcekitd_uid_t
public let is_system: sourcekitd_uid_t
public let kind: sourcekitd_uid_t
Expand Down Expand Up @@ -115,6 +116,7 @@ public struct sourcekitd_keys {
expression_type_list = api.uid_get_from_cstr("key.expression_type_list")!
filepath = api.uid_get_from_cstr("key.filepath")!
fixits = api.uid_get_from_cstr("key.fixits")!
groupname = api.uid_get_from_cstr("key.groupname")!
id = api.uid_get_from_cstr("key.id")!
is_system = api.uid_get_from_cstr("key.is_system")!
kind = api.uid_get_from_cstr("key.kind")!
Expand Down Expand Up @@ -176,6 +178,7 @@ public struct sourcekitd_requests {
public let codecomplete_close: sourcekitd_uid_t
public let cursorinfo: sourcekitd_uid_t
public let expression_type: sourcekitd_uid_t
public let find_usr: sourcekitd_uid_t
public let variable_type: sourcekitd_uid_t
public let relatedidents: sourcekitd_uid_t
public let semantic_refactoring: sourcekitd_uid_t
Expand All @@ -192,6 +195,7 @@ public struct sourcekitd_requests {
codecomplete_close = api.uid_get_from_cstr("source.request.codecomplete.close")!
cursorinfo = api.uid_get_from_cstr("source.request.cursorinfo")!
expression_type = api.uid_get_from_cstr("source.request.expression.type")!
find_usr = api.uid_get_from_cstr("source.request.editor.find_usr")!
variable_type = api.uid_get_from_cstr("source.request.variable.type")!
relatedidents = api.uid_get_from_cstr("source.request.relatedidents")!
semantic_refactoring = api.uid_get_from_cstr("source.request.semantic.refactoring")!
Expand Down
51 changes: 37 additions & 14 deletions Sources/SourceKitLSP/SourceKitServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1292,20 +1292,7 @@ extension SourceKitServer {

// If this symbol is a module then generate a textual interface
if case .success(let symbols) = result, let symbol = symbols.first, symbol.kind == .module, let name = symbol.name {
let openInterface = OpenInterfaceRequest(textDocument: req.params.textDocument, name: name)
let request = Request(openInterface, id: req.id, clientID: ObjectIdentifier(self),
cancellation: req.cancellationToken, reply: { (result: Result<OpenInterfaceRequest.Response, ResponseError>) in
switch result {
case .success(let interfaceDetails?):
let loc = Location(uri: interfaceDetails.uri, range: Range(Position(line: 0, utf16index: 0)))
req.reply(.locations([loc]))
case .success(nil):
req.reply(.failure(.unknown("Could not generate Swift Interface for \(name)")))
case .failure(let error):
req.reply(.failure(error))
}
})
languageService.openInterface(request)
self.respondWithInterface(req, moduleName: name, symbolUSR: nil, languageService: languageService)
return
}

Expand All @@ -1320,6 +1307,19 @@ extension SourceKitServer {

switch extractedResult {
case .success(let resolved):
// if first resolved location is in `.swiftinterface` file. Use moduleName to return
// textual interface
if let firstResolved = resolved.first,
let moduleName = firstResolved.occurrence?.location.moduleName,
firstResolved.location.uri.fileURL?.pathExtension == "swiftinterface" {
self.respondWithInterface(
req,
moduleName: moduleName,
symbolUSR: firstResolved.occurrence?.symbol.usr,
languageService: languageService
)
return
}
let locs = resolved.map(\.location)
// If we're unable to handle the definition request using our index, see if the
// language service can handle it (e.g. clangd can provide AST based definitions).
Expand All @@ -1339,6 +1339,29 @@ extension SourceKitServer {
languageService.symbolInfo(request)
}

func respondWithInterface(
_ req: Request<DefinitionRequest>,
moduleName: String,
symbolUSR: String?,
languageService: ToolchainLanguageServer
) {
let openInterface = OpenInterfaceRequest(textDocument: req.params.textDocument, name: moduleName, symbolUSR: symbolUSR)
let request = Request(openInterface, id: req.id, clientID: ObjectIdentifier(self),
cancellation: req.cancellationToken, reply: { (result: Result<OpenInterfaceRequest.Response, ResponseError>) in
switch result {
case .success(let interfaceDetails?):
let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0)
let loc = Location(uri: interfaceDetails.uri, range: Range(position))
req.reply(.locations([loc]))
case .success(nil):
req.reply(.failure(.unknown("Could not generate Swift Interface for \(moduleName)")))
case .failure(let error):
req.reply(.failure(error))
}
})
languageService.openInterface(request)
}

func implementation(
_ req: Request<ImplementationRequest>,
workspace: Workspace,
Expand Down
99 changes: 86 additions & 13 deletions Sources/SourceKitLSP/Swift/OpenInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,46 @@ import Foundation
import SourceKitD
import LanguageServerProtocol
import LSPLogging
import SKSupport

struct InterfaceInfo {
var contents: String
}

struct FindUSRInfo {
let position: Position?
}

extension SwiftLanguageServer {
public func openInterface(_ request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>) {
let uri = request.params.textDocument.uri
let moduleName = request.params.name
let moduleName = request.params.moduleName
let name = request.params.name
let symbol = request.params.symbolUSR
self.queue.async {
let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(moduleName).swiftinterface")
let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(name).swiftinterface")
let interfaceDocURI = DocumentURI(interfaceFilePath)
self._openInterface(request: request, uri: uri, name: moduleName, interfaceURI: interfaceDocURI) { result in
switch result {
case .success(let interfaceInfo):
do {
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
request.reply(.success(InterfaceDetails(uri: interfaceDocURI)))
} catch {
request.reply(.failure(ResponseError.unknown(error.localizedDescription)))
// has interface already been generated
if let snapshot = self.documentManager.latestSnapshot(interfaceDocURI) {
self._findUSRAndRespond(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol)
} else {
// generate interface
self._openInterface(request: request, uri: uri, name: moduleName, interfaceURI: interfaceDocURI) { result in
switch result {
case .success(let interfaceInfo):
do {
// write to file
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
// store snapshot
let snapshot = try self.documentManager.open(interfaceDocURI, language: .swift, version: 0, text: interfaceInfo.contents)
self._findUSRAndRespond(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol)
} catch {
request.reply(.failure(ResponseError.unknown(error.localizedDescription)))
}
case .failure(let error):
log("open interface failed: \(error)", level: .warning)
request.reply(.failure(ResponseError(error)))
}
case .failure(let error):
log("open interface failed: \(error)", level: .warning)
request.reply(.failure(ResponseError(error)))
}
}
}
Expand All @@ -60,6 +76,9 @@ extension SwiftLanguageServer {
let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)
skreq[keys.request] = requests.editor_open_interface
skreq[keys.modulename] = name
if request.params.groupNames.count > 0 {
skreq[keys.groupname] = request.params.groupNames
}
skreq[keys.name] = interfaceURI.pseudoPath
skreq[keys.synthesizedextensions] = 1
if let compileCommand = self.commandsByFile[uri] {
Expand All @@ -81,4 +100,58 @@ extension SwiftLanguageServer {
}
}
}

private func _findUSRAndRespond(
request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>,
uri: DocumentURI,
snapshot: DocumentSnapshot,
symbol: String?
) {
self._findUSR(request: request, uri: uri, snapshot: snapshot, symbol: symbol) { result in
switch result {
case .success(let info):
request.reply(.success(InterfaceDetails(uri: uri, position: info.position)))
case .failure:
request.reply(.success(InterfaceDetails(uri: uri, position: nil)))
}
}
}

private func _findUSR(
request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>,
uri: DocumentURI,
snapshot: DocumentSnapshot,
symbol: String?,
completion: @escaping (Swift.Result<FindUSRInfo, SKDError>) -> Void
) {
guard let symbol = symbol else {
return completion(.success(FindUSRInfo(position: nil)))
}
let keys = self.keys
let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)

skreq[keys.request] = requests.find_usr
skreq[keys.sourcefile] = uri.pseudoPath
skreq[keys.usr] = symbol

let handle = self.sourcekitd.send(skreq, self.queue) { result in
switch result {
case .success(let dict):
if let offset: Int = dict[keys.offset],
let position = snapshot.positionOf(utf8Offset: offset) {
return completion(.success(FindUSRInfo(position: position)))
} else {
return completion(.success(FindUSRInfo(position: nil)))
}
case .failure(let error):
return completion(.failure(error))
}
}

if let handle = handle {
request.cancellationToken.addCancellationHandler { [weak self] in
self?.sourcekitd.cancel(handle)
}
}
}
}
Loading

0 comments on commit ff667c2

Please sign in to comment.