Skip to content

Commit af39b02

Browse files
committed
Add Semantic Functionality to Macro Expansion Reference Documents (including Nested Macro Expansion)
1 parent a3bb2d7 commit af39b02

12 files changed

+234
-69
lines changed

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1674,10 +1674,18 @@ extension SourceKitLSPServer {
16741674
}
16751675

16761676
func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? {
1677-
guard let uri = req.textDocument?.uri else {
1677+
guard let reqURI = req.textDocument?.uri,
1678+
let uri =
1679+
if let primaryFileURI = (try? ReferenceDocumentURL(from: reqURI))?.primaryFile {
1680+
primaryFileURI
1681+
} else {
1682+
reqURI
1683+
}
1684+
else {
16781685
logger.error("Attempted to perform executeCommand request without an URL")
16791686
return nil
16801687
}
1688+
16811689
guard let workspace = await workspaceForDocument(uri: uri) else {
16821690
throw ResponseError.workspaceNotOpen(uri)
16831691
}

Sources/SourceKitLSP/Swift/CursorInfo.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,10 @@ extension SwiftLanguageService {
158158
keys.cancelOnSubsequentRequest: 0,
159159
keys.offset: offsetRange.lowerBound,
160160
keys.length: offsetRange.upperBound != offsetRange.lowerBound ? offsetRange.count : nil,
161-
keys.sourceFile: snapshot.uri.pseudoPath,
161+
keys.sourceFile: snapshot.uri.actualFilePath,
162+
keys.primaryFile: (try? ReferenceDocumentURL(from: snapshot.uri))?.primaryFile.pseudoPath,
162163
keys.compilerArgs: await self.buildSettings(for: uri)?.compilerArgs as [SKDRequestValue]?,
163164
])
164-
165165
appendAdditionalParameters?(skreq)
166166

167167
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)

Sources/SourceKitLSP/Swift/DiagnosticReportManager.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ actor DiagnosticReportManager {
104104

105105
let skreq = sourcekitd.dictionary([
106106
keys.request: requests.diagnostics,
107-
keys.sourceFile: snapshot.uri.pseudoPath,
107+
keys.sourceFile: snapshot.uri.actualFilePath,
108+
keys.primaryFile: (try? ReferenceDocumentURL(from: snapshot.uri))?.primaryFile.pseudoPath,
108109
keys.compilerArgs: compilerArgs as [SKDRequestValue],
109110
])
110111

Sources/SourceKitLSP/Swift/MacroExpansion.swift

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ struct MacroExpansion: RefactoringResponse {
4444
}
4545
}
4646

47+
actor GeneratedMacroExpansionsStorage {
48+
static var shared = GeneratedMacroExpansionsStorage()
49+
private init() {}
50+
51+
private var generatedMacroExpansions: [String: String] = [:]
52+
53+
func addOrUpdateMacroExpansion(havingBufferName bufferName: String, withContents content: String) {
54+
generatedMacroExpansions[bufferName] = content
55+
}
56+
57+
func retrieveAndDeleteMacroExpansion(havingBufferName bufferName: String) -> String? {
58+
return generatedMacroExpansions.removeValue(forKey: bufferName)
59+
}
60+
}
61+
4762
extension SwiftLanguageService {
4863
/// Handles the `ExpandMacroCommand`.
4964
///
@@ -62,9 +77,9 @@ extension SwiftLanguageService {
6277
throw ResponseError.unknown("Connection to the editor closed")
6378
}
6479

65-
guard let primaryFileURL = expandMacroCommand.textDocument.uri.fileURL else {
66-
throw ResponseError.unknown("Given URI is not a file URL")
67-
}
80+
let referenceDocumentURL = try? ReferenceDocumentURL(from: expandMacroCommand.textDocument.uri)
81+
let primaryFileURL =
82+
referenceDocumentURL?.primaryFile.arbitrarySchemeURL ?? expandMacroCommand.textDocument.uri.arbitrarySchemeURL
6883

6984
let expansion = try await self.refactoring(expandMacroCommand)
7085

@@ -74,6 +89,11 @@ extension SwiftLanguageService {
7489
var macroExpansionReferenceDocumentURLs: [ReferenceDocumentURL] = []
7590
for macroEdit in expansion.edits {
7691
if let bufferName = macroEdit.bufferName {
92+
await GeneratedMacroExpansionsStorage.shared.addOrUpdateMacroExpansion(
93+
havingBufferName: bufferName,
94+
withContents: macroEdit.newText
95+
)
96+
7797
let macroExpansionReferenceDocumentURLData =
7898
ReferenceDocumentURL.macroExpansion(
7999
MacroExpansionReferenceDocumentURLData(
@@ -108,10 +128,24 @@ extension SwiftLanguageService {
108128
let expansionURIs = try macroExpansionReferenceDocumentURLs.map {
109129
return DocumentURI(try $0.url)
110130
}
131+
111132
Task {
133+
let (uri, position) =
134+
if let referenceDocumentURL, case let .macroExpansion(referenceDocumentURLData) = referenceDocumentURL {
135+
(
136+
referenceDocumentURL.primaryFile,
137+
referenceDocumentURLData.macroExpansionEditRange.lowerBound
138+
)
139+
} else {
140+
(
141+
expandMacroCommand.textDocument.uri,
142+
expandMacroCommand.positionRange.lowerBound
143+
)
144+
}
145+
112146
let req = PeekDocumentsRequest(
113-
uri: expandMacroCommand.textDocument.uri,
114-
position: expandMacroCommand.positionRange.lowerBound,
147+
uri: uri,
148+
position: position,
115149
locations: expansionURIs
116150
)
117151

@@ -179,22 +213,15 @@ extension SwiftLanguageService {
179213
}
180214
}
181215

182-
func expandMacro(macroExpansionURLData: MacroExpansionReferenceDocumentURLData) async throws -> String {
183-
let expandMacroCommand = ExpandMacroCommand(
184-
positionRange: macroExpansionURLData.selectionRange,
185-
textDocument: TextDocumentIdentifier(macroExpansionURLData.primaryFile)
186-
)
187-
188-
let expansion = try await self.refactoring(expandMacroCommand)
189-
216+
func getMacroExpansion(macroExpansionURLData: MacroExpansionReferenceDocumentURLData) async throws -> String {
190217
guard
191-
let macroExpansionEdit = expansion.edits.filter({
192-
$0.bufferName == macroExpansionURLData.bufferName
193-
}).only
218+
let content = await GeneratedMacroExpansionsStorage.shared.retrieveAndDeleteMacroExpansion(
219+
havingBufferName: macroExpansionURLData.bufferName
220+
)
194221
else {
195222
throw ResponseError.unknown("Macro expansion edit doesn't exist")
196223
}
197224

198-
return macroExpansionEdit.newText
225+
return content
199226
}
200227
}

Sources/SourceKitLSP/Swift/MacroExpansionReferenceDocumentURLData.swift

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ package struct MacroExpansionReferenceDocumentURLData {
7171

7272
guard let primaryFileURL = URL(string: "file://\(primaryFilePath)") else {
7373
throw ReferenceDocumentURLError(
74-
description: "Unable to parse source file url"
74+
description: "Unable to parse primary file url"
7575
)
7676
}
7777

@@ -82,6 +82,61 @@ package struct MacroExpansionReferenceDocumentURLData {
8282
self.macroExpansionEditRange = try Self.parse(displayName: displayName)
8383
}
8484

85+
/// The file path of the document that originally contains the contents of this reference document.
86+
/// This is used since `sourcekitd` cannot understand reference document urls.
87+
///
88+
/// For any `ReferenceDocumentURL.macroExpansion`, its `actualFilePath` will be its sourcekitd `bufferName`
89+
///
90+
/// *Example:*
91+
///
92+
/// User's source File:
93+
/// URL: `file:///path/to/swift_file.swift`
94+
/// ```swift
95+
/// let a = 10
96+
/// let b = 5
97+
/// print(#stringify(a + b))
98+
/// ```
99+
///
100+
/// Generated content of reference document url:
101+
/// URL:
102+
/// `sourcekit-lsp://swift-macro-expansion/L3C7-L3C23.swift?primaryFilePath=/path/to/swift_file.swift&fromLine=3&fromColumn=8&toLine=3&toColumn=8&bufferName=@__swift_macro_..._Stringify_.swift`
103+
/// ```swift
104+
/// (a + b, "a + b")
105+
/// ```
106+
///
107+
/// Here the `actualFilePath` of the reference document url is `@__swift_macro_..._Stringify_.swift`
108+
///
109+
/// *NOTE*: In case of nested macro expansion reference documents, the `actualFilePath` will be their corresponding
110+
/// `bufferName`s
111+
package var actualFilePath: String {
112+
bufferName
113+
}
114+
115+
/// The URI of the document from which this reference document was derived.
116+
/// This is used to determine the workspace and language service that is used to generate the reference document.
117+
///
118+
/// *Example:*
119+
///
120+
/// User's source File:
121+
/// URL: `file://path/to/swift_file.swift`
122+
/// ```swift
123+
/// let a = 10
124+
/// let b = 5
125+
/// print(#stringify(a + b))
126+
/// ```
127+
///
128+
/// Generated content of reference document url:
129+
/// URL:
130+
/// `sourcekit-lsp://swift-macro-expansion/L3C7-L3C23.swift?primaryFilePath=/path/to/swift_file.swift&fromLine=3&fromColumn=8&toLine=3&toColumn=8&bufferName=@__swift_macro_..._Stringify_.swift`
131+
/// ```swift
132+
/// (a + b, "a + b")
133+
/// ```
134+
///
135+
/// Here the `primaryFile` of the reference document url is a `DocumentURI`
136+
/// with the following url: `file:///path/to/swift_file.swift`
137+
///
138+
/// *NOTE*: In case of nested macro expansion reference documents, they all will have the same `primaryFile`
139+
/// as that of the first macro expansion reference document i.e. `primaryFile` doesn't change.
85140
package var primaryFile: DocumentURI {
86141
DocumentURI(primaryFileURL)
87142
}

Sources/SourceKitLSP/Swift/OpenInterface.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,20 @@ extension SwiftLanguageService {
4444
symbol: symbol
4545
)
4646
} else {
47+
let primaryDocument =
48+
if let referenceDocument = try? ReferenceDocumentURL(from: document) {
49+
referenceDocument.primaryFile
50+
} else {
51+
document
52+
}
53+
4754
let interfaceInfo = try await self.generatedInterfaceInfo(
48-
document: document,
55+
document: primaryDocument,
4956
moduleName: moduleName,
5057
groupName: groupName,
5158
interfaceURI: interfaceDocURI
5259
)
60+
5361
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
5462
let snapshot = DocumentSnapshot(
5563
uri: interfaceDocURI,

Sources/SourceKitLSP/Swift/RefactoringResponse.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,28 +102,35 @@ extension SwiftLanguageService {
102102

103103
let uri = refactorCommand.textDocument.uri
104104
let snapshot = try self.documentManager.latestSnapshot(uri)
105+
let referenceDocument = try? ReferenceDocumentURL(from: snapshot.uri)
106+
105107
let line = refactorCommand.positionRange.lowerBound.line
106108
let utf16Column = refactorCommand.positionRange.lowerBound.utf16index
107109
let utf8Column = snapshot.lineTable.utf8ColumnAt(line: line, utf16Column: utf16Column)
110+
let length = snapshot.utf8OffsetRange(of: refactorCommand.positionRange).count
111+
let buildSettings =
112+
await self.buildSettings(for: referenceDocument?.primaryFile ?? snapshot.uri)?.compilerArgs as [SKDRequestValue]?
108113

109114
let skreq = sourcekitd.dictionary([
110115
keys.request: self.requests.semanticRefactoring,
111116
// Preferred name for e.g. an extracted variable.
112117
// Empty string means sourcekitd chooses a name automatically.
113118
keys.name: "",
114-
keys.sourceFile: uri.pseudoPath,
119+
keys.sourceFile: uri.actualFilePath,
120+
keys.primaryFile: referenceDocument?.primaryFile.pseudoPath,
115121
// LSP is zero based, but this request is 1 based.
116122
keys.line: line + 1,
117123
keys.column: utf8Column + 1,
118-
keys.length: snapshot.utf8OffsetRange(of: refactorCommand.positionRange).count,
124+
keys.length: length,
119125
keys.actionUID: self.sourcekitd.api.uid_get_from_cstr(refactorCommand.actionString)!,
120-
keys.compilerArgs: await self.buildSettings(for: snapshot.uri)?.compilerArgs as [SKDRequestValue]?,
126+
keys.compilerArgs: buildSettings,
121127
])
122128

123129
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
124130
guard let refactor = T.Response(refactorCommand.title, dict, snapshot, self.keys) else {
125131
throw SemanticRefactoringError.noEditsNeeded(uri)
126132
}
133+
127134
return refactor
128135
}
129136
}

Sources/SourceKitLSP/Swift/ReferenceDocumentURL.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ package enum ReferenceDocumentURL {
6363

6464
switch documentType {
6565
case MacroExpansionReferenceDocumentURLData.documentType:
66-
guard let queryItems = URLComponents(string: url.absoluteString)?.queryItems else {
66+
guard
67+
let queryItems = URLComponents(string: url.absoluteString.removingPercentEncoding ?? url.absoluteString)?
68+
.queryItems
69+
else {
6770
throw ReferenceDocumentURLError(
6871
description: "No queryItems passed for macro expansion reference document: \(url)"
6972
)
@@ -85,8 +88,17 @@ package enum ReferenceDocumentURL {
8588
}
8689
}
8790

88-
/// The URI of the document from which this reference document was derived. This is used to determine the
89-
/// workspace and language service that is used to generate the reference document.
91+
/// The file path of the document that originally contains the contents of this reference document.
92+
/// This is used since sourcekitd cannot understand reference document urls.
93+
var actualFilePath: String {
94+
switch self {
95+
case let .macroExpansion(data):
96+
data.actualFilePath
97+
}
98+
}
99+
100+
/// The URI of the source file from which this reference document was derived.
101+
/// This is used to determine the workspace and language service that is used to generate the reference document.
90102
var primaryFile: DocumentURI {
91103
switch self {
92104
case let .macroExpansion(data):
@@ -95,6 +107,20 @@ package enum ReferenceDocumentURL {
95107
}
96108
}
97109

110+
extension DocumentURI {
111+
/// The file path of the document that originally contains the contents of the reference document
112+
/// of this `DocumentURI`. This is used since sourcekitd cannot understand reference document urls.
113+
///
114+
/// If the `DocumentURI` is not a reference document, this gives the `pseudoPath`
115+
var actualFilePath: String {
116+
if let referenceDocument = try? ReferenceDocumentURL(from: self) {
117+
referenceDocument.actualFilePath
118+
} else {
119+
self.pseudoPath
120+
}
121+
}
122+
}
123+
98124
package struct ReferenceDocumentURLError: Error, CustomStringConvertible {
99125
package var description: String
100126

Sources/SourceKitLSP/Swift/RelatedIdentifiers.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ extension SwiftLanguageService {
6969
keys.request: requests.relatedIdents,
7070
keys.cancelOnSubsequentRequest: 0,
7171
keys.offset: snapshot.utf8Offset(of: position),
72-
keys.sourceFile: snapshot.uri.pseudoPath,
72+
keys.sourceFile: snapshot.uri.actualFilePath,
73+
keys.primaryFile: (try? ReferenceDocumentURL(from: snapshot.uri))?.primaryFile.pseudoPath,
7374
keys.includeNonEditableBaseNames: includeNonEditableBaseNames ? 1 : 0,
7475
keys.compilerArgs: await self.buildSettings(for: snapshot.uri)?.compilerArgs as [SKDRequestValue]?,
7576
])

Sources/SourceKitLSP/Swift/SemanticTokens.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ extension SwiftLanguageService {
2626

2727
let skreq = sourcekitd.dictionary([
2828
keys.request: requests.semanticTokens,
29-
keys.sourceFile: snapshot.uri.pseudoPath,
29+
keys.sourceFile: snapshot.uri.actualFilePath,
30+
keys.primaryFile: (try? ReferenceDocumentURL(from: snapshot.uri))?.primaryFile.pseudoPath,
3031
keys.compilerArgs: buildSettings.compilerArgs as [SKDRequestValue],
3132
])
3233

0 commit comments

Comments
 (0)