Skip to content

Support creation of SKDRequest(Dictionary|Array) from literals #1008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions Sources/SourceKitD/SKDRequestArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ import Musl
import CRT
#endif

extension SourceKitD {
/// Create a `SKDRequestArray` from the given array.
public func array(_ array: [SKDValue]) -> SKDRequestArray {
let result = SKDRequestArray(sourcekitd: self)
for element in array {
result.append(element)
}
return result
}
}

public final class SKDRequestArray {
public let array: sourcekitd_object_t?
public let sourcekitd: SourceKitD
Expand All @@ -33,15 +44,32 @@ public final class SKDRequestArray {
sourcekitd.api.request_release(array)
}

public func append(_ value: String) {
sourcekitd.api.request_array_set_string(array, -1, value)
}

public func append(_ value: SKDRequestDictionary) {
sourcekitd.api.request_array_set_value(array, -1, value.dict)
public func append(_ newValue: SKDValue) {
switch newValue {
case let newValue as String:
sourcekitd.api.request_array_set_string(array, -1, newValue)
case let newValue as Int:
sourcekitd.api.request_array_set_int64(array, -1, Int64(newValue))
case let newValue as sourcekitd_uid_t:
sourcekitd.api.request_array_set_uid(array, -1, newValue)
case let newValue as SKDRequestDictionary:
sourcekitd.api.request_array_set_value(array, -1, newValue.dict)
case let newValue as SKDRequestArray:
sourcekitd.api.request_array_set_value(array, -1, newValue.array)
case let newValue as Array<SKDValue>:
self.append(sourcekitd.array(newValue))
case let newValue as Dictionary<sourcekitd_uid_t, SKDValue>:
self.append(sourcekitd.dictionary(newValue))
case let newValue as Optional<SKDValue>:
if let newValue {
self.append(newValue)
}
default:
preconditionFailure("Unknown type conforming to SKDValueProtocol")
}
}

public static func += (array: SKDRequestArray, other: some Sequence<SKDRequestDictionary>) {
public static func += (array: SKDRequestArray, other: some Sequence<SKDValue>) {
for item in other {
array.append(item)
}
Expand Down
76 changes: 50 additions & 26 deletions Sources/SourceKitD/SKDRequestDictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,34 @@ import Musl
import CRT
#endif

/// Values that can be stored in a `SKDRequestDictionary`.
///
/// - Warning: `SKDRequestDictionary.subscript` and `SKDRequestArray.append`
/// switch exhaustively over this protocol.
/// Do not add new conformances without adding a new case in the subscript and
/// `append` function.
public protocol SKDValue {}

extension String: SKDValue {}
extension Int: SKDValue {}
extension sourcekitd_uid_t: SKDValue {}
extension SKDRequestDictionary: SKDValue {}
extension SKDRequestArray: SKDValue {}
extension Array<SKDValue>: SKDValue {}
extension Dictionary<sourcekitd_uid_t, SKDValue>: SKDValue {}
extension Optional: SKDValue where Wrapped: SKDValue {}

extension SourceKitD {
/// Create a `SKDRequestDictionary` from the given dictionary.
public func dictionary(_ dict: [sourcekitd_uid_t: SKDValue]) -> SKDRequestDictionary {
let result = SKDRequestDictionary(sourcekitd: self)
for (key, value) in dict {
result.set(key, to: value)
}
return result
}
}

public final class SKDRequestDictionary {
public let dict: sourcekitd_object_t?
public let sourcekitd: SourceKitD
Expand All @@ -34,34 +62,30 @@ public final class SKDRequestDictionary {
sourcekitd.api.request_release(dict)
}

public subscript(key: sourcekitd_uid_t?) -> String {
get { fatalError("request is set-only") }
set { sourcekitd.api.request_dictionary_set_string(dict, key, newValue) }
}
public subscript(key: sourcekitd_uid_t?) -> Int {
get { fatalError("request is set-only") }
set { sourcekitd.api.request_dictionary_set_int64(dict, key, Int64(newValue)) }
}
public subscript(key: sourcekitd_uid_t?) -> sourcekitd_uid_t? {
get { fatalError("request is set-only") }
set { sourcekitd.api.request_dictionary_set_uid(dict, key, newValue) }
}
public subscript(key: sourcekitd_uid_t?) -> SKDRequestDictionary {
get { fatalError("request is set-only") }
set { sourcekitd.api.request_dictionary_set_value(dict, key, newValue.dict) }
}
public subscript<S>(key: sourcekitd_uid_t?) -> S where S: Sequence, S.Element == String {
get { fatalError("request is set-only") }
set {
let array = SKDRequestArray(sourcekitd: sourcekitd)
newValue.forEach { array.append($0) }
sourcekitd.api.request_dictionary_set_value(dict, key, array.array)
public func set(_ key: sourcekitd_uid_t, to newValue: SKDValue) {
switch newValue {
case let newValue as String:
sourcekitd.api.request_dictionary_set_string(dict, key, newValue)
case let newValue as Int:
sourcekitd.api.request_dictionary_set_int64(dict, key, Int64(newValue))
case let newValue as sourcekitd_uid_t:
sourcekitd.api.request_dictionary_set_uid(dict, key, newValue)
case let newValue as SKDRequestDictionary:
sourcekitd.api.request_dictionary_set_value(dict, key, newValue.dict)
case let newValue as SKDRequestArray:
sourcekitd.api.request_dictionary_set_value(dict, key, newValue.array)
case let newValue as Array<SKDValue>:
self.set(key, to: sourcekitd.array(newValue))
case let newValue as Dictionary<sourcekitd_uid_t, SKDValue>:
self.set(key, to: sourcekitd.dictionary(newValue))
case let newValue as Optional<SKDValue>:
if let newValue {
self.set(key, to: newValue)
}
default:
preconditionFailure("Unknown type conforming to SKDValueProtocol")
}
}
public subscript(key: sourcekitd_uid_t?) -> SKDRequestArray {
get { fatalError("request is set-only") }
set { sourcekitd.api.request_dictionary_set_value(dict, key, newValue.array) }
}
}

extension SKDRequestDictionary: CustomStringConvertible {
Expand Down
44 changes: 22 additions & 22 deletions Sources/SourceKitLSP/Rename.swift
Original file line number Diff line number Diff line change
Expand Up @@ -383,28 +383,28 @@ extension SwiftLanguageServer {
oldName: String,
in snapshot: DocumentSnapshot
) async throws -> [SyntacticRenameName] {
let locations = SKDRequestArray(sourcekitd: sourcekitd)
locations += renameLocations.map { renameLocation in
let skRenameLocation = SKDRequestDictionary(sourcekitd: sourcekitd)
skRenameLocation[keys.line] = renameLocation.line
skRenameLocation[keys.column] = renameLocation.utf8Column
skRenameLocation[keys.nameType] = renameLocation.usage.uid(keys: keys)
return skRenameLocation
}
let renameLocation = SKDRequestDictionary(sourcekitd: sourcekitd)
renameLocation[keys.locations] = locations
renameLocation[keys.name] = oldName

let renameLocations = SKDRequestArray(sourcekitd: sourcekitd)
renameLocations.append(renameLocation)

let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)
skreq[keys.request] = requests.find_syntactic_rename_ranges
skreq[keys.sourcefile] = snapshot.uri.pseudoPath
// find-syntactic-rename-ranges is a syntactic sourcekitd request that doesn't use the in-memory file snapshot.
// We need to send the source text again.
skreq[keys.sourcetext] = snapshot.text
skreq[keys.renamelocations] = renameLocations
let locations = sourcekitd.array(
renameLocations.map { renameLocation in
sourcekitd.dictionary([
keys.line: renameLocation.line,
keys.column: renameLocation.utf8Column,
keys.nameType: renameLocation.usage.uid(keys: keys),
])
}
)
let renameLocation = sourcekitd.dictionary([
keys.locations: locations,
keys.name: oldName,
])

let skreq = sourcekitd.dictionary([
keys.request: requests.find_syntactic_rename_ranges,
keys.sourcefile: snapshot.uri.pseudoPath,
// find-syntactic-rename-ranges is a syntactic sourcekitd request that doesn't use the in-memory file snapshot.
// We need to send the source text again.
keys.sourcetext: snapshot.text,
keys.renamelocations: [renameLocation],
])

let syntacticRenameRangesResponse = try await sourcekitd.send(skreq, fileContents: snapshot.text)
guard let categorizedRanges: SKDResponseArray = syntacticRenameRangesResponse[keys.categorizedranges] else {
Expand Down
64 changes: 32 additions & 32 deletions Sources/SourceKitLSP/Swift/CodeCompletionSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,15 @@ class CodeCompletionSession {
throw ResponseError(code: .invalidRequest, message: "open must use the original snapshot")
}

let req = SKDRequestDictionary(sourcekitd: sourcekitd)
req[keys.request] = sourcekitd.requests.codecomplete_open
req[keys.offset] = utf8StartOffset
req[keys.name] = uri.pseudoPath
req[keys.sourcefile] = uri.pseudoPath
req[keys.sourcetext] = snapshot.text
req[keys.codecomplete_options] = optionsDictionary(filterText: filterText, options: options)
if let compileCommand = compileCommand {
req[keys.compilerargs] = compileCommand.compilerArgs
}
let req = sourcekitd.dictionary([
keys.request: sourcekitd.requests.codecomplete_open,
keys.offset: utf8StartOffset,
keys.name: uri.pseudoPath,
keys.sourcefile: uri.pseudoPath,
keys.sourcetext: snapshot.text,
keys.codecomplete_options: optionsDictionary(filterText: filterText, options: options),
keys.compilerargs: compileCommand?.compilerArgs as [SKDValue]?,
])

let dict = try await sourcekitd.send(req, fileContents: snapshot.text)
self.state = .open
Expand Down Expand Up @@ -229,11 +228,12 @@ class CodeCompletionSession {
// FIXME: Assertion for prefix of snapshot matching what we started with.

logger.info("Updating code completion session: \(self, privacy: .private) filter=\(filterText)")
let req = SKDRequestDictionary(sourcekitd: sourcekitd)
req[keys.request] = sourcekitd.requests.codecomplete_update
req[keys.offset] = utf8StartOffset
req[keys.name] = uri.pseudoPath
req[keys.codecomplete_options] = optionsDictionary(filterText: filterText, options: options)
let req = sourcekitd.dictionary([
keys.request: sourcekitd.requests.codecomplete_update,
keys.offset: utf8StartOffset,
keys.name: uri.pseudoPath,
keys.codecomplete_options: optionsDictionary(filterText: filterText, options: options),
])

let dict = try await sourcekitd.send(req, fileContents: snapshot.text)
guard let completions: SKDResponseArray = dict[keys.results] else {
Expand All @@ -253,19 +253,18 @@ class CodeCompletionSession {
filterText: String,
options: SKCompletionOptions
) -> SKDRequestDictionary {
let dict = SKDRequestDictionary(sourcekitd: sourcekitd)
// Sorting and priority options.
dict[keys.codecomplete_hideunderscores] = 0
dict[keys.codecomplete_hidelowpriority] = 0
dict[keys.codecomplete_hidebyname] = 0
dict[keys.codecomplete_addinneroperators] = 0
dict[keys.codecomplete_callpatternheuristics] = 0
dict[keys.codecomplete_showtopnonliteralresults] = 0
// Filtering options.
dict[keys.codecomplete_filtertext] = filterText
if let maxResults = options.maxResults {
dict[keys.codecomplete_requestlimit] = maxResults
}
let dict = sourcekitd.dictionary([
// Sorting and priority options.
keys.codecomplete_hideunderscores: 0,
keys.codecomplete_hidelowpriority: 0,
keys.codecomplete_hidebyname: 0,
keys.codecomplete_addinneroperators: 0,
keys.codecomplete_callpatternheuristics: 0,
keys.codecomplete_showtopnonliteralresults: 0,
// Filtering options.
keys.codecomplete_filtertext: filterText,
keys.codecomplete_requestlimit: options.maxResults,
])
return dict
}

Expand All @@ -275,10 +274,11 @@ class CodeCompletionSession {
// Already closed, nothing to do.
break
case .open:
let req = SKDRequestDictionary(sourcekitd: sourcekitd)
req[keys.request] = sourcekitd.requests.codecomplete_close
req[keys.offset] = self.utf8StartOffset
req[keys.name] = self.snapshot.uri.pseudoPath
let req = sourcekitd.dictionary([
keys.request: sourcekitd.requests.codecomplete_close,
keys.offset: utf8StartOffset,
keys.name: snapshot.uri.pseudoPath,
])
logger.info("Closing code completion session: \(self, privacy: .private)")
_ = try? await sourcekitd.send(req, fileContents: nil)
self.state = .closed
Expand Down
21 changes: 8 additions & 13 deletions Sources/SourceKitLSP/Swift/CursorInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,14 @@ extension SwiftLanguageServer {

let keys = self.keys

let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)
skreq[keys.request] = requests.cursorinfo
skreq[keys.cancelOnSubsequentRequest] = 0
skreq[keys.offset] = offsetRange.lowerBound
if offsetRange.upperBound != offsetRange.lowerBound {
skreq[keys.length] = offsetRange.count
}
skreq[keys.sourcefile] = snapshot.uri.pseudoPath

// FIXME: SourceKit should probably cache this for us.
if let compileCommand = await self.buildSettings(for: uri) {
skreq[keys.compilerargs] = compileCommand.compilerArgs
}
let skreq = sourcekitd.dictionary([
keys.request: requests.cursorinfo,
keys.cancelOnSubsequentRequest: 0,
keys.offset: offsetRange.lowerBound,
keys.length: offsetRange.upperBound != offsetRange.lowerBound ? offsetRange.count : nil,
keys.sourcefile: snapshot.uri.pseudoPath,
keys.compilerargs: await self.buildSettings(for: uri)?.compilerArgs as [SKDValue]?,
])

appendAdditionalParameters?(skreq)

Expand Down
29 changes: 13 additions & 16 deletions Sources/SourceKitLSP/Swift/OpenInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,14 @@ extension SwiftLanguageServer {
interfaceURI: DocumentURI
) async throws -> InterfaceInfo {
let keys = self.keys
let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)
skreq[keys.request] = requests.editor_open_interface
skreq[keys.modulename] = name
if request.groupNames.count > 0 {
skreq[keys.groupname] = request.groupNames
}
skreq[keys.name] = interfaceURI.pseudoPath
skreq[keys.synthesizedextensions] = 1
if let compileCommand = await self.buildSettings(for: uri) {
skreq[keys.compilerargs] = compileCommand.compilerArgs
}
let skreq = sourcekitd.dictionary([
keys.request: requests.editor_open_interface,
keys.modulename: name,
keys.groupname: request.groupNames.isEmpty ? nil : request.groupNames as [SKDValue],
keys.name: interfaceURI.pseudoPath,
keys.synthesizedextensions: 1,
keys.compilerargs: await self.buildSettings(for: uri)?.compilerArgs as [SKDValue]?,
])

let dict = try await self.sourcekitd.send(skreq, fileContents: nil)
return InterfaceInfo(contents: dict[keys.sourcetext] ?? "")
Expand All @@ -97,11 +94,11 @@ extension SwiftLanguageServer {
return InterfaceDetails(uri: uri, 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 skreq = sourcekitd.dictionary([
keys.request: requests.find_usr,
keys.sourcefile: uri.pseudoPath,
keys.usr: symbol,
])

let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
if let offset: Int = dict[keys.offset],
Expand Down
19 changes: 8 additions & 11 deletions Sources/SourceKitLSP/Swift/RelatedIdentifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,14 @@ extension SwiftLanguageServer {
throw ResponseError.unknown("invalid position \(position)")
}

let skreq = SKDRequestDictionary(sourcekitd: self.sourcekitd)
skreq[keys.request] = self.requests.relatedidents
skreq[keys.cancelOnSubsequentRequest] = 0
skreq[keys.offset] = offset
skreq[keys.sourcefile] = snapshot.uri.pseudoPath
skreq[keys.includeNonEditableBaseNames] = includeNonEditableBaseNames ? 1 : 0

// FIXME: SourceKit should probably cache this for us.
if let compileCommand = await self.buildSettings(for: snapshot.uri) {
skreq[keys.compilerargs] = compileCommand.compilerArgs
}
let skreq = sourcekitd.dictionary([
keys.request: requests.relatedidents,
keys.cancelOnSubsequentRequest: 0,
keys.offset: offset,
keys.sourcefile: snapshot.uri.pseudoPath,
keys.includeNonEditableBaseNames: includeNonEditableBaseNames ? 1 : 0,
keys.compilerargs: await self.buildSettings(for: snapshot.uri)?.compilerArgs as [SKDValue]?,
])

let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)

Expand Down
Loading