Skip to content

Commit 027ce8d

Browse files
committed
[ASTGen/Macros] Introduce a Swift-side SourceManager into ASTGen.
Add SourceManager that can keep track of multiple source file syntax nodes along with their external representations. The source manager can emit diagnostics into any of those files, including tracking any explicitly "detached" syntax nodes used for macro expansion. Make sure we detach syntax nodes before passing them to macro implementations, so they cannot see more of the source file than they are permitted. We hadn't been doing this before (by accident), and doing so motivated the introduction of the SourceManager. Additionally, perform operator folding on macro arguments as part of detaching them. Macro clients shouldn't have to do this, and moreover, when clients do this, they lose the ability to easily emit diagnostics on the now-folded nodes.
1 parent 182950b commit 027ce8d

File tree

7 files changed

+320
-53
lines changed

7 files changed

+320
-53
lines changed

lib/ASTGen/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ if (SWIFT_SWIFT_PARSER)
2828
Sources/ASTGen/Macros.swift
2929
Sources/ASTGen/Misc.swift
3030
Sources/ASTGen/SourceFile.swift
31+
Sources/ASTGen/SourceManager.swift
3132
Sources/ASTGen/Stmts.swift
3233
Sources/ASTGen/Types.swift
3334
)

lib/ASTGen/Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ let package = Package(
2626
dependencies: [
2727
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
2828
.product(name: "SwiftSyntax", package: "swift-syntax"),
29+
.product(name: "SwiftOperators", package: "swift-syntax"),
2930
.product(name: "SwiftParser", package: "swift-syntax"),
3031
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
3132
],

lib/ASTGen/Sources/ASTGen/Diagnostics.swift

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,124 @@ func emitDiagnostic(
123123
}
124124
}
125125

126+
extension SourceManager {
127+
private func diagnoseSingle(
128+
message: String,
129+
severity: DiagnosticSeverity,
130+
node: some SyntaxProtocol,
131+
position: AbsolutePosition,
132+
highlights: [Syntax] = [],
133+
fixItChanges: [FixIt.Change] = []
134+
) {
135+
// Map severity
136+
let bridgedSeverity: BridgedDiagnosticSeverity
137+
switch severity {
138+
case .error: bridgedSeverity = .error
139+
case .note: bridgedSeverity = .note
140+
case .warning: bridgedSeverity = .warning
141+
}
142+
143+
// Emit the diagnostic
144+
var mutableMessage = message
145+
let diag = mutableMessage.withUTF8 { messageBuffer in
146+
SwiftDiagnostic_create(
147+
cxxDiagnosticEngine, bridgedSeverity,
148+
cxxSourceLocation(for: node, at: position),
149+
messageBuffer.baseAddress, messageBuffer.count
150+
)
151+
}
152+
153+
// Emit highlights
154+
for highlight in highlights {
155+
SwiftDiagnostic_highlight(
156+
diag,
157+
cxxSourceLocation(for: highlight),
158+
cxxSourceLocation(for: highlight, at: highlight.endPosition)
159+
)
160+
}
161+
162+
// Emit changes for a Fix-It.
163+
for change in fixItChanges {
164+
let replaceStartLoc: CxxSourceLoc?
165+
let replaceEndLoc: CxxSourceLoc?
166+
var newText: String
167+
168+
switch change {
169+
case .replace(let oldNode, let newNode):
170+
replaceStartLoc = cxxSourceLocation(for: oldNode)
171+
replaceEndLoc = cxxSourceLocation(
172+
for: oldNode,
173+
at: oldNode.endPosition
174+
)
175+
newText = newNode.description
176+
177+
case .replaceLeadingTrivia(let oldToken, let newTrivia):
178+
replaceStartLoc = cxxSourceLocation(for: oldToken)
179+
replaceEndLoc = cxxSourceLocation(
180+
for: oldToken,
181+
at: oldToken.positionAfterSkippingLeadingTrivia
182+
)
183+
newText = newTrivia.description
184+
185+
case .replaceTrailingTrivia(let oldToken, let newTrivia):
186+
replaceStartLoc = cxxSourceLocation(
187+
for: oldToken,
188+
at: oldToken.endPositionBeforeTrailingTrivia)
189+
replaceEndLoc = cxxSourceLocation(
190+
for: oldToken,
191+
at: oldToken.endPosition
192+
)
193+
newText = newTrivia.description
194+
}
195+
196+
newText.withUTF8 { textBuffer in
197+
SwiftDiagnostic_fixItReplace(
198+
diag, replaceStartLoc, replaceEndLoc,
199+
textBuffer.baseAddress, textBuffer.count
200+
)
201+
}
202+
}
203+
204+
SwiftDiagnostic_finish(diag);
205+
}
206+
207+
/// Emit a diagnostic via the C++ diagnostic engine.
208+
func diagnose(
209+
diagnostic: Diagnostic,
210+
messageSuffix: String? = nil
211+
) {
212+
// Emit the main diagnostic.
213+
diagnoseSingle(
214+
message: diagnostic.diagMessage.message + (messageSuffix ?? ""),
215+
severity: diagnostic.diagMessage.severity,
216+
node: diagnostic.node,
217+
position: diagnostic.position,
218+
highlights: diagnostic.highlights
219+
)
220+
221+
// Emit Fix-Its.
222+
for fixIt in diagnostic.fixIts {
223+
diagnoseSingle(
224+
message: fixIt.message.message,
225+
severity: .note,
226+
node: diagnostic.node,
227+
position: diagnostic.position,
228+
fixItChanges: fixIt.changes.changes
229+
)
230+
}
231+
232+
// Emit any notes as follow-ons.
233+
for note in diagnostic.notes {
234+
diagnoseSingle(
235+
message: note.message,
236+
severity: .note,
237+
node: note.node,
238+
position: note.position
239+
)
240+
}
241+
}
242+
}
243+
126244
/// A set of queued diagnostics created by the C++ compiler and rendered
127245
/// via the swift-syntax renderer.
128246
struct QueuedDiagnostics {

lib/ASTGen/Sources/ASTGen/Macros.swift

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import SwiftDiagnostics
2+
import SwiftOperators
23
import SwiftParser
34
import SwiftSyntax
45
import SwiftSyntaxMacros
@@ -94,18 +95,6 @@ func allocateUTF8String(
9495
}
9596
}
9697

97-
extension String {
98-
/// Drop everything up to and including the last '/' from the string.
99-
fileprivate func withoutPath() -> String {
100-
// Only keep everything after the last slash.
101-
if let lastSlash = lastIndex(of: "/") {
102-
return String(self[index(after: lastSlash)...])
103-
}
104-
105-
return self
106-
}
107-
}
108-
10998
/// Diagnostic message used for thrown errors.
11099
fileprivate struct ThrownErrorDiagnostic: DiagnosticMessage {
111100
let message: String
@@ -151,14 +140,11 @@ func evaluateMacro(
151140
return -1
152141
}
153142

154-
let context = BasicMacroExpansionContext(
155-
sourceFiles: [
156-
sourceFilePtr.pointee.syntax : .init(
157-
moduleName: sourceFilePtr.pointee.moduleName,
158-
fullFilePath: sourceFilePtr.pointee.fileName
159-
)
160-
]
161-
)
143+
// Create a source manager. This should probably persist and be given to us.
144+
let sourceManager = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
145+
sourceManager.insert(sourceFilePtr)
146+
147+
let context = sourceManager.createMacroExpansionContext()
162148

163149
guard let parentSyntax = token.parent else {
164150
print("not on a macro expansion node: \(token.recursiveDescription)")
@@ -183,7 +169,10 @@ func evaluateMacro(
183169
macroName = parentExpansion.macro.text
184170
evaluatedSyntax = Syntax(
185171
try exprMacro.expansion(
186-
of: context.detach(parentExpansion),
172+
of: sourceManager.detach(
173+
parentExpansion, in: context,
174+
foldingWith: OperatorTable.standardOperators
175+
),
187176
in: context
188177
)
189178
)
@@ -195,7 +184,7 @@ func evaluateMacro(
195184
return -1
196185
}
197186
let decls = try declMacro.expansion(
198-
of: context.detach(parentExpansion),
187+
of: sourceManager.detach(parentExpansion, in: context),
199188
in: context
200189
)
201190
macroName = parentExpansion.macro.text
@@ -219,9 +208,7 @@ func evaluateMacro(
219208

220209
// Emit diagnostics accumulated in the context.
221210
for diag in context.diagnostics {
222-
emitDiagnostic(
223-
diagEnginePtr: diagEnginePtr,
224-
sourceFileBuffer: .init(mutating: sourceFilePtr.pointee.buffer),
211+
sourceManager.diagnose(
225212
diagnostic: diag,
226213
messageSuffix: " (from macro '\(macroName)')"
227214
)
@@ -330,19 +317,15 @@ func expandAttachedMacro(
330317
to: ExportedSourceFile.self, capacity: 1
331318
)
332319

333-
// Record the source file(s).
334-
var sourceFiles: [SourceFileSyntax : BasicMacroExpansionContext.KnownSourceFile] = [:]
335-
sourceFiles[attributeSourceFile.pointee.syntax] = .init(
336-
moduleName: attributeSourceFile.pointee.moduleName,
337-
fullFilePath: attributeSourceFile.pointee.fileName
338-
)
339-
sourceFiles[declarationSourceFilePtr.pointee.syntax] = .init(
340-
moduleName: declarationSourceFilePtr.pointee.moduleName,
341-
fullFilePath: declarationSourceFilePtr.pointee.fileName
342-
)
320+
// Create a source manager covering the files we know about.
321+
let sourceManager = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
322+
sourceManager.insert(attributeSourceFile)
323+
sourceManager.insert(declarationSourceFilePtr)
343324

344-
let context = BasicMacroExpansionContext(sourceFiles: sourceFiles)
325+
// Create an expansion context
326+
let context = sourceManager.createMacroExpansionContext()
345327

328+
let macroName = customAttrNode.attributeName.description
346329
var evaluatedSyntaxStr: String
347330
do {
348331
switch (macro, macroRole) {
@@ -417,8 +400,13 @@ func expandAttachedMacro(
417400
return 1
418401
}
419402

420-
// FIXME: Emit diagnostics, but how do we figure out which source file to
421-
// use?
403+
// Emit diagnostics accumulated in the context.
404+
for diag in context.diagnostics {
405+
sourceManager.diagnose(
406+
diagnostic: diag,
407+
messageSuffix: " (from macro '\(macroName)')"
408+
)
409+
}
422410

423411
// Form the result buffer for our caller.
424412
evaluatedSyntaxStr.withUTF8 { utf8 in

0 commit comments

Comments
 (0)