Skip to content

Commit 49277f7

Browse files
committed
[Macros] Move most macro definition checking into ASTGen
In preparation for supporting macros that are defined in terms of other macros, adopt macro definition checking provided by the `MacroDeclSyntax.checkDefinition` operation (implemented in swift-syntax). Use this in lieu of the checking on the C++ side. This required me to finally fix an issue with the source ranges for Fix-Its, where we were replacing the leading/trailing trivia of nodes along with the node itself, even though that's incorrect: we should only replce the node itself, and there are other Fix-It kinds for replacing leading or trailing trivia.
1 parent 0d74959 commit 49277f7

File tree

9 files changed

+309
-99
lines changed

9 files changed

+309
-99
lines changed

include/swift/AST/CASTBridging.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ typedef enum ENUM_EXTENSIBILITY_ATTR(open) BridgedDiagnosticSeverity : long {
102102

103103
typedef void* BridgedDiagnostic;
104104

105+
typedef enum ENUM_EXTENSIBILITY_ATTR(open) BridgedMacroDefinitionKind : ptrdiff_t {
106+
/// An expanded macro.
107+
BridgedExpandedMacro = 0,
108+
/// An external macro, spelled with either the old spelling (Module.Type)
109+
/// or the new spelling `#externalMacro(module: "Module", type: "Type")`.
110+
BridgedExternalMacro,
111+
/// The builtin definition for "externalMacro".
112+
BridgedBuiltinExternalMacro
113+
} BridgedMacroDefinitionKind;
114+
105115
#ifdef __cplusplus
106116
extern "C" {
107117

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7005,13 +7005,6 @@ ERROR(macro_unsupported,none,
70057005
"macros are not supported in this compiler", ())
70067006
ERROR(macro_recursive,none,
70077007
"recursive expansion of macro %0", (DeclName))
7008-
WARNING(macro_definition_old_style,none,
7009-
"external macro definitions are now written using #externalMacro", ())
7010-
WARNING(macro_definition_unknown_builtin,none,
7011-
"ignoring definition of unknown builtin macro %0", (Identifier))
7012-
ERROR(macro_definition_not_expansion,none,
7013-
"macro must itself be defined by a macro expansion such as "
7014-
"'#externalMacro(...)'", ())
70157008
ERROR(macro_definition_unsupported,none,
70167009
"macro definitions other than '#externalMacro(...)' are unsupported", ())
70177010
ERROR(external_macro_arg_not_type_name,none,

lib/ASTGen/Sources/ASTGen/Diagnostics.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,13 @@ extension SourceManager {
170170

171171
switch change {
172172
case .replace(let oldNode, let newNode):
173-
replaceStartLoc = cxxSourceLocation(for: oldNode)
173+
replaceStartLoc = cxxSourceLocation(
174+
for: oldNode,
175+
at: oldNode.positionAfterSkippingLeadingTrivia
176+
)
174177
replaceEndLoc = cxxSourceLocation(
175178
for: oldNode,
176-
at: oldNode.endPosition
179+
at: oldNode.endPositionBeforeTrailingTrivia
177180
)
178181
newText = newNode.description
179182

lib/ASTGen/Sources/ASTGen/Macros.swift

Lines changed: 236 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import CASTBridging
1314
import SwiftDiagnostics
1415
import SwiftOperators
1516
import SwiftParser
@@ -155,14 +156,240 @@ func allocateUTF8String(
155156
}
156157
}
157158

158-
/// Diagnostic message used for thrown errors.
159-
fileprivate struct ThrownErrorDiagnostic: DiagnosticMessage {
160-
let message: String
159+
/// Diagnostics produced here.
160+
enum ASTGenMacroDiagnostic: DiagnosticMessage, FixItMessage {
161+
case thrownError(Error)
162+
case oldStyleExternalMacro
163+
case useExternalMacro
164+
case unknownBuiltin(String)
165+
case notStringLiteralArgument(String)
161166

162-
var severity: DiagnosticSeverity { .error }
167+
var message: String {
168+
switch self {
169+
case .thrownError(let error):
170+
return String(describing: error)
171+
172+
case .oldStyleExternalMacro:
173+
return "external macro definitions are now written using #externalMacro"
174+
175+
case .useExternalMacro:
176+
return "use '#externalMacro'"
177+
178+
case .unknownBuiltin(let type):
179+
return "ignoring definition of unknown builtin macro \(type)"
180+
181+
case .notStringLiteralArgument(let kind):
182+
return "argument to `#externalMacro` must be a string literal naming the external macro's \(kind)"
183+
}
184+
}
185+
186+
var severity: DiagnosticSeverity {
187+
switch self {
188+
case .thrownError, .notStringLiteralArgument:
189+
return .error
190+
191+
case .oldStyleExternalMacro, .unknownBuiltin:
192+
return .warning
193+
194+
case .useExternalMacro:
195+
return .note
196+
}
197+
}
163198

164199
var diagnosticID: MessageID {
165-
.init(domain: "SwiftSyntaxMacros", id: "ThrownErrorDiagnostic")
200+
.init(domain: "Swift", id: "\(self)")
201+
}
202+
203+
var fixItID: MessageID { diagnosticID }
204+
}
205+
206+
/// Treat the given expression as a string literal, which should contain a
207+
/// single identifier.
208+
fileprivate func identifierFromStringLiteral(_ node: ExprSyntax) -> String? {
209+
guard let stringLiteral = node.as(StringLiteralExprSyntax.self),
210+
stringLiteral.segments.count == 1,
211+
let segment = stringLiteral.segments.first,
212+
case .stringSegment(let stringSegment) = segment else {
213+
return nil
214+
}
215+
216+
return stringSegment.content.text
217+
}
218+
219+
/// Check a macro definition, producing a description of that macro definition
220+
/// for use in macro expansion.
221+
///
222+
/// When the resulting macro requires expansion, the result will come in
223+
/// two parts:
224+
///
225+
/// - Returns: -1 on failure, BridgedMacroDefinitionKind on success. When the
226+
/// successful result is "expanded macro", `replacementsPtr` will point to a
227+
/// number of "replacements" to perform when expanding that macro. Each
228+
/// replacement is a textual replacement of use of a macro parameter with the
229+
/// source text of the corresponding argument, and is represented as a triple
230+
/// (start offset, end offset, parameter index): the [start offset, end offset)
231+
/// range in the macro expansion expression should be replaced with the
232+
/// argument matching the corresponding parameter.
233+
@_cdecl("swift_ASTGen_checkMacroDefinition")
234+
func checkMacroDefinition(
235+
diagEnginePtr: UnsafeMutablePointer<UInt8>,
236+
sourceFilePtr: UnsafeRawPointer,
237+
macroLocationPtr: UnsafePointer<UInt8>,
238+
externalMacroPointer: UnsafeMutablePointer<UnsafePointer<UInt8>?>,
239+
externalMacroLength: UnsafeMutablePointer<Int>,
240+
replacementsPtr: UnsafeMutablePointer<UnsafeMutablePointer<Int>?>,
241+
numReplacementsPtr: UnsafeMutablePointer<Int>
242+
) -> Int {
243+
// Clear out the "out" parameters.
244+
externalMacroPointer.pointee = nil
245+
externalMacroLength.pointee = 0
246+
replacementsPtr.pointee = nil
247+
numReplacementsPtr.pointee = 0
248+
249+
let sourceFilePtr = sourceFilePtr.bindMemory(to: ExportedSourceFile.self, capacity: 1)
250+
251+
// Find the macro declaration.
252+
guard let macroDecl = findSyntaxNodeInSourceFile(
253+
sourceFilePtr: sourceFilePtr,
254+
sourceLocationPtr: macroLocationPtr,
255+
type: MacroDeclSyntax.self
256+
) else {
257+
// FIXME: Produce an error
258+
return -1
259+
}
260+
261+
// Check the definition
262+
do {
263+
let definition = try macroDecl.checkDefinition()
264+
switch definition {
265+
case let .deprecatedExternal(node: node, module: module, type: type):
266+
// Check for known builtins.
267+
if module == "Builtin" {
268+
switch type {
269+
case "ExternalMacro":
270+
return BridgedMacroDefinitionKind.builtinExternalMacro.rawValue
271+
272+
default:
273+
// Warn about the unknown builtin.
274+
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
275+
srcMgr.insert(sourceFilePtr)
276+
srcMgr.diagnose(
277+
diagnostic: .init(
278+
node: node,
279+
message: ASTGenMacroDiagnostic.unknownBuiltin(type)
280+
)
281+
)
282+
283+
return -1
284+
}
285+
}
286+
287+
// Form the "ModuleName.TypeName" result string.
288+
(externalMacroPointer.pointee, externalMacroLength.pointee) =
289+
allocateUTF8String("\(module).\(type)", nullTerminated: true)
290+
291+
// Translate this into a use of #externalMacro.
292+
let expansionSourceSyntax: ExprSyntax =
293+
"#externalMacro(module: \(literal: module), type: \(literal: type))"
294+
295+
// Warn about the use of old-style external macro syntax here.
296+
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
297+
srcMgr.insert(sourceFilePtr)
298+
srcMgr.diagnose(
299+
diagnostic: .init(
300+
node: node,
301+
message: ASTGenMacroDiagnostic.oldStyleExternalMacro,
302+
fixIts: [
303+
FixIt(
304+
message: ASTGenMacroDiagnostic.useExternalMacro,
305+
changes: [
306+
FixIt.Change.replace(
307+
oldNode: node,
308+
newNode: Syntax(expansionSourceSyntax)
309+
)
310+
]
311+
)
312+
]
313+
)
314+
)
315+
return BridgedMacroDefinitionKind.externalMacro.rawValue
316+
317+
case let .expansion(expansionSyntax, replacements: replacements)
318+
where expansionSyntax.macro.text == "externalMacro":
319+
// Extract the identifier from the "module" argument.
320+
guard let firstArg = expansionSyntax.argumentList.first,
321+
let firstArgLabel = firstArg.label?.text,
322+
firstArgLabel == "module",
323+
let module = identifierFromStringLiteral(firstArg.expression) else {
324+
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
325+
srcMgr.insert(sourceFilePtr)
326+
srcMgr.diagnose(
327+
diagnostic: .init(
328+
node: Syntax(expansionSyntax),
329+
message: ASTGenMacroDiagnostic.notStringLiteralArgument("module")
330+
)
331+
)
332+
return -1
333+
}
334+
335+
// Extract the identifier from the "type" argument.
336+
guard let secondArg = expansionSyntax.argumentList.dropFirst().first,
337+
let secondArgLabel = secondArg.label?.text,
338+
secondArgLabel == "type",
339+
let type = identifierFromStringLiteral(secondArg.expression) else {
340+
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
341+
srcMgr.insert(sourceFilePtr)
342+
srcMgr.diagnose(
343+
diagnostic: .init(
344+
node: Syntax(expansionSyntax),
345+
message: ASTGenMacroDiagnostic.notStringLiteralArgument("type")
346+
)
347+
)
348+
return -1
349+
}
350+
351+
// Form the "ModuleName.TypeName" result string.
352+
(externalMacroPointer.pointee, externalMacroLength.pointee) =
353+
allocateUTF8String("\(module).\(type)", nullTerminated: true)
354+
return BridgedMacroDefinitionKind.externalMacro.rawValue
355+
356+
case let .expansion(expansionSyntax, replacements: replacements):
357+
// If there are no replacements, we're done.
358+
if replacements.isEmpty {
359+
return BridgedMacroDefinitionKind.expandedMacro.rawValue
360+
}
361+
362+
// The replacements are triples: (startOffset, endOffset, parameter index).
363+
let replacementBuffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 3 * replacements.count)
364+
for (index, replacement) in replacements.enumerated() {
365+
let expansionStart = expansionSyntax.positionAfterSkippingLeadingTrivia.utf8Offset
366+
367+
replacementBuffer[index * 3] = replacement.reference.positionAfterSkippingLeadingTrivia.utf8Offset - expansionStart
368+
replacementBuffer[index * 3 + 1] = replacement.reference.endPositionBeforeTrailingTrivia.utf8Offset - expansionStart
369+
replacementBuffer[index * 3 + 2] = replacement.parameterIndex
370+
}
371+
372+
replacementsPtr.pointee = replacementBuffer.baseAddress
373+
numReplacementsPtr.pointee = replacements.count
374+
return BridgedMacroDefinitionKind.expandedMacro.rawValue
375+
}
376+
} catch let errDiags as DiagnosticsError {
377+
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
378+
srcMgr.insert(sourceFilePtr)
379+
for diag in errDiags.diagnostics {
380+
srcMgr.diagnose(diagnostic: diag)
381+
}
382+
return -1
383+
} catch let error {
384+
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
385+
srcMgr.insert(sourceFilePtr)
386+
srcMgr.diagnose(
387+
diagnostic: .init(
388+
node: Syntax(macroDecl),
389+
message: ASTGenMacroDiagnostic.thrownError(error)
390+
)
391+
)
392+
return -1
166393
}
167394
}
168395

@@ -286,7 +513,7 @@ func expandFreestandingMacroIPC(
286513
node: macroSyntax,
287514
// FIXME: This is probably a plugin communication error.
288515
// The error might not be relevant as the diagnostic message.
289-
message: ThrownErrorDiagnostic(message: String(describing: error))
516+
message: ASTGenMacroDiagnostic.thrownError(error)
290517
),
291518
messageSuffix: " (from macro '\(macroName)')"
292519
)
@@ -378,7 +605,7 @@ func expandFreestandingMacroInProcess(
378605
sourceManager.diagnose(
379606
diagnostic: Diagnostic(
380607
node: macroSyntax,
381-
message: ThrownErrorDiagnostic(message: String(describing: error))
608+
message: ASTGenMacroDiagnostic.thrownError(error)
382609
),
383610
messageSuffix: " (from macro '\(macroName)')"
384611
)
@@ -631,7 +858,7 @@ func expandAttachedMacroIPC(
631858
node: Syntax(declarationNode),
632859
// FIXME: This is probably a plugin communication error.
633860
// The error might not be relevant as the diagnostic message.
634-
message: ThrownErrorDiagnostic(message: String(describing: error))
861+
message: ASTGenMacroDiagnostic.thrownError(error)
635862
),
636863
messageSuffix: " (from macro '\(macroName)')"
637864
)
@@ -811,7 +1038,7 @@ func expandAttachedMacroInProcess(
8111038
sourceManager.diagnose(
8121039
diagnostic: Diagnostic(
8131040
node: Syntax(declarationNode),
814-
message: ThrownErrorDiagnostic(message: String(describing: error))
1041+
message: ASTGenMacroDiagnostic.thrownError(error)
8151042
),
8161043
messageSuffix: " (from macro '\(macroName)')"
8171044
)

0 commit comments

Comments
 (0)