Files
swift-mirror/lib/ASTGen/Sources/MacroEvaluation/Macros.swift
Michael Gottesman 9d59dbed17 [swift-settings] Now that we aren't using it immediately, remove it from tree.
We can always get it back from the git history.

rdar://150695113
2025-05-05 13:39:03 -07:00

790 lines
26 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import ASTBridging
import BasicBridging
@_spi(PluginMessage) @_spi(ExperimentalLanguageFeature) import SwiftCompilerPluginMessageHandling
import SwiftDiagnostics
import SwiftParser
import SwiftSyntax
@_spi(ExperimentalLanguageFeature) @_spi(Compiler) import SwiftSyntaxMacroExpansion
import swiftASTGen
struct ExportedExternalMacro {
var moduleName: String
var typeName: String
var plugin: CompilerPlugin
}
extension MacroRole {
init(rawMacroRole: UInt8) {
switch rawMacroRole {
case 0: self = .expression
case 1: self = .declaration
case 2: self = .accessor
case 3: self = .memberAttribute
case 4: self = .member
case 5: self = .peer
case 6: self = .conformance
case 7: self = .codeItem
case 8: self = .`extension`
case 9: self = .preamble
case 10: self = .body
default: fatalError("unknown macro role")
}
}
}
@_cdecl("swift_Macros_resolveExternalMacro")
public func resolveExternalMacro(
moduleName: UnsafePointer<CChar>,
typeName: UnsafePointer<CChar>,
pluginOpaqueHandle: UnsafeMutableRawPointer
) -> UnsafeRawPointer {
// NOTE: This doesn't actually resolve anything.
// Executable plugins is "trusted" to have the macro implementation. If not,
// the actual expansion fails.
let exportedPtr = UnsafeMutablePointer<ExportedExternalMacro>.allocate(capacity: 1)
exportedPtr.initialize(
to: .init(
moduleName: String(cString: moduleName),
typeName: String(cString: typeName),
plugin: CompilerPlugin(opaqueHandle: pluginOpaqueHandle)
)
)
return UnsafeRawPointer(exportedPtr)
}
@_cdecl("swift_Macros_destroyExternalMacro")
public func destroyExternalMacro(
macroPtr: UnsafeMutableRawPointer
) {
let macroPtr = macroPtr.assumingMemoryBound(to: ExportedExternalMacro.self)
macroPtr.deinitialize(count: 1)
macroPtr.deallocate()
}
/// Diagnostics produced here.
enum ASTGenMacroDiagnostic: DiagnosticMessage, FixItMessage {
case thrownError(Error)
case oldStyleExternalMacro
case useExternalMacro
case unknownBuiltin(String)
case notStringLiteralArgument(String)
var message: String {
switch self {
case .thrownError(let error):
return String(describing: error)
case .oldStyleExternalMacro:
return "external macro definitions are now written using #externalMacro"
case .useExternalMacro:
return "use '#externalMacro'"
case .unknownBuiltin(let type):
return "ignoring definition of unknown builtin macro \(type)"
case .notStringLiteralArgument(let kind):
return "argument to `#externalMacro` must be a string literal naming the external macro's \(kind)"
}
}
var severity: DiagnosticSeverity {
switch self {
case .thrownError, .notStringLiteralArgument:
return .error
case .oldStyleExternalMacro, .unknownBuiltin:
return .warning
case .useExternalMacro:
return .note
}
}
var diagnosticID: MessageID {
.init(domain: "Swift", id: "\(self)")
}
var fixItID: MessageID { diagnosticID }
}
/// Treat the given expression as a string literal, which should contain a
/// single identifier.
fileprivate func identifierFromStringLiteral(_ node: ExprSyntax) -> String? {
guard let stringLiteral = node.as(StringLiteralExprSyntax.self),
stringLiteral.segments.count == 1,
let segment = stringLiteral.segments.first,
case .stringSegment(let stringSegment) = segment
else {
return nil
}
return stringSegment.content.text
}
/// Checks if the macro expression used as an default argument has any issues.
///
/// - Returns: `true` if all restrictions are satisfied, `false` if diagnostics
/// are emitted.
@_cdecl("swift_Macros_checkDefaultArgumentMacroExpression")
func checkDefaultArgumentMacroExpression(
diagEnginePtr: UnsafeMutableRawPointer,
sourceFilePtr: UnsafeRawPointer,
macroLocationPtr: UnsafePointer<UInt8>
) -> Bool {
let sourceFilePtr = sourceFilePtr.bindMemory(to: ExportedSourceFile.self, capacity: 1)
// Find the macro expression.
guard
let macroExpr = findSyntaxNodeInSourceFile(
sourceFilePtr: sourceFilePtr,
sourceLocationPtr: macroLocationPtr,
type: MacroExpansionExprSyntax.self
)
else {
// FIXME: Produce an error
return false
}
do {
try macroExpr.checkDefaultArgumentMacroExpression()
return true
} catch let errDiags as DiagnosticsError {
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
srcMgr.insert(sourceFilePtr)
for diag in errDiags.diagnostics {
srcMgr.diagnose(diagnostic: diag)
}
return false
} catch let error {
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
srcMgr.insert(sourceFilePtr)
srcMgr.diagnose(
diagnostic: .init(
node: macroExpr,
message: ASTGenMacroDiagnostic.thrownError(error)
)
)
return false
}
}
/// Check a macro definition, producing a description of that macro definition
/// for use in macro expansion.
///
/// When the resulting macro requires expansion, the result will come in
/// two parts:
///
/// - Returns: -1 on failure, BridgedMacroDefinitionKind on success. When the
/// successful result is "expanded macro", `replacementsPtr` will point to a
/// number of "replacements" to perform when expanding that macro. Each
/// replacement is a textual replacement of use of a macro parameter with the
/// source text of the corresponding argument, and is represented as a triple
/// (start offset, end offset, parameter index): the [start offset, end offset)
/// range in the macro expansion expression should be replaced with the
/// argument matching the corresponding parameter.
@_cdecl("swift_Macros_checkMacroDefinition")
func checkMacroDefinition(
diagEnginePtr: UnsafeMutableRawPointer,
sourceFileBuffer: BridgedStringRef,
macroDeclText: BridgedStringRef,
externalMacroOutPtr: UnsafeMutablePointer<BridgedStringRef>,
replacementsPtr: UnsafeMutablePointer<UnsafeMutablePointer<Int>?>,
numReplacementsPtr: UnsafeMutablePointer<Int>,
genericReplacementsPtr: UnsafeMutablePointer<UnsafeMutablePointer<Int>?>,
numGenericReplacementsPtr: UnsafeMutablePointer<Int>
) -> Int {
// Assert "out" parameters are initialized.
assert(externalMacroOutPtr.pointee.isEmpty)
assert(replacementsPtr.pointee == nil && numReplacementsPtr.pointee == 0)
// Parse 'macro' decl.
// FIXME: Use 'ExportedSourceFile' when C++ parser is replaced.
let textBuffer = UnsafeBufferPointer<UInt8>(start: macroDeclText.data, count: macroDeclText.count)
var parser = Parser(textBuffer)
guard let macroDecl = DeclSyntax.parse(from: &parser).as(MacroDeclSyntax.self) else {
// FIXME: Produce an error
return -1
}
func diagnose(diagnostic: Diagnostic) {
emitDiagnostic(
diagnosticEngine: BridgedDiagnosticEngine(raw: diagEnginePtr),
sourceFileBuffer: UnsafeBufferPointer(start: sourceFileBuffer.data, count: sourceFileBuffer.count),
sourceFileBufferOffset: macroDeclText.data! - sourceFileBuffer.data!,
diagnostic: diagnostic,
diagnosticSeverity: diagnostic.diagMessage.severity
)
}
// Check the definition
do {
let definition = try macroDecl.checkDefinition()
switch definition {
case let .deprecatedExternal(node: node, module: module, type: type):
// Check for known builtins.
if module == "Builtin" {
switch type {
case "ExternalMacro":
return Int(BridgedMacroDefinitionKind.builtinExternalMacro.rawValue)
case "IsolationMacro":
return Int(BridgedMacroDefinitionKind.builtinIsolationMacro.rawValue)
// These builtins don't exist, but are put into the standard library at
// least for documentation purposes right now. Don't emit a warning for
// them, but do fail operation.
case "FileIDMacro",
"FilePathMacro",
"FileMacro",
"FunctionMacro",
"LineMacro",
"ColumnMacro",
"DSOHandleMacro",
"WarningMacro",
"ErrorMacro":
return -1
default:
// Warn about the unknown builtin.
diagnose(
diagnostic: .init(
node: node,
message: ASTGenMacroDiagnostic.unknownBuiltin(type)
)
)
return -1
}
}
// Form the "ModuleName.TypeName" result string.
externalMacroOutPtr.pointee =
allocateBridgedString("\(module).\(type)")
// Translate this into a use of #externalMacro.
let expansionSourceSyntax: ExprSyntax =
"#externalMacro(module: \(literal: module), type: \(literal: type))"
// Warn about the use of old-style external macro syntax here.
diagnose(
diagnostic: .init(
node: node,
message: ASTGenMacroDiagnostic.oldStyleExternalMacro,
fixIts: [
FixIt(
message: ASTGenMacroDiagnostic.useExternalMacro,
changes: [
FixIt.Change.replace(
oldNode: node,
newNode: Syntax(expansionSourceSyntax)
)
]
)
]
)
)
return Int(BridgedMacroDefinitionKind.externalMacro.rawValue)
case let .expansion(expansionSyntax, replacements: _, genericReplacements: _)
where expansionSyntax.macroName.text == "externalMacro":
// Extract the identifier from the "module" argument.
guard let firstArg = expansionSyntax.arguments.first,
let firstArgLabel = firstArg.label?.text,
firstArgLabel == "module",
let module = identifierFromStringLiteral(firstArg.expression)
else {
diagnose(
diagnostic: .init(
node: Syntax(expansionSyntax),
message: ASTGenMacroDiagnostic.notStringLiteralArgument("module")
)
)
return -1
}
// Extract the identifier from the "type" argument.
guard let secondArg = expansionSyntax.arguments.dropFirst().first,
let secondArgLabel = secondArg.label?.text,
secondArgLabel == "type",
let type = identifierFromStringLiteral(secondArg.expression)
else {
diagnose(
diagnostic: .init(
node: Syntax(expansionSyntax),
message: ASTGenMacroDiagnostic.notStringLiteralArgument("type")
)
)
return -1
}
// Form the "ModuleName.TypeName" result string.
externalMacroOutPtr.pointee =
allocateBridgedString("\(module).\(type)")
return Int(BridgedMacroDefinitionKind.externalMacro.rawValue)
case let .expansion(expansionSyntax,
replacements: replacements, genericReplacements: genericReplacements):
// Provide the expansion syntax.
externalMacroOutPtr.pointee =
allocateBridgedString(expansionSyntax.trimmedDescription)
// If there are no replacements, we're done.
let totalReplacementsCount = replacements.count + genericReplacements.count
guard totalReplacementsCount > 0 else {
return Int(BridgedMacroDefinitionKind.expandedMacro.rawValue)
}
// The replacements are triples: (startOffset, endOffset, parameter index).
let replacementBuffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 3 * replacements.count)
for (index, replacement) in replacements.enumerated() {
let expansionStart = expansionSyntax.positionAfterSkippingLeadingTrivia.utf8Offset
replacementBuffer[index * 3] =
replacement.reference.positionAfterSkippingLeadingTrivia.utf8Offset - expansionStart
replacementBuffer[index * 3 + 1] =
replacement.reference.endPositionBeforeTrailingTrivia.utf8Offset - expansionStart
replacementBuffer[index * 3 + 2] = replacement.parameterIndex
}
replacementsPtr.pointee = replacementBuffer.baseAddress
numReplacementsPtr.pointee = replacements.count
// The replacements are triples: (startOffset, endOffset, parameter index).
let genericReplacementBuffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 3 * genericReplacements.count)
for (index, genericReplacement) in genericReplacements.enumerated() {
let expansionStart = expansionSyntax.positionAfterSkippingLeadingTrivia.utf8Offset
genericReplacementBuffer[index * 3] =
genericReplacement.reference.positionAfterSkippingLeadingTrivia.utf8Offset - expansionStart
genericReplacementBuffer[index * 3 + 1] =
genericReplacement.reference.endPositionBeforeTrailingTrivia.utf8Offset - expansionStart
genericReplacementBuffer[index * 3 + 2] =
genericReplacement.parameterIndex
}
genericReplacementsPtr.pointee = genericReplacementBuffer.baseAddress
numGenericReplacementsPtr.pointee = genericReplacements.count
return Int(BridgedMacroDefinitionKind.expandedMacro.rawValue)
}
} catch let errDiags as DiagnosticsError {
for diag in errDiags.diagnostics {
diagnose(diagnostic: diag)
}
return -1
} catch let error {
diagnose(
diagnostic: .init(
node: Syntax(macroDecl),
message: ASTGenMacroDiagnostic.thrownError(error)
)
)
return -1
}
}
@_cdecl("swift_Macros_freeExpansionReplacements")
public func freeExpansionReplacements(
pointer: UnsafeMutablePointer<Int>?,
numReplacements: Int
) {
UnsafeMutableBufferPointer(start: pointer, count: numReplacements).deallocate()
}
// Make an expansion result for '@_cdecl' function caller.
func makeExpansionOutputResult(
expandedSource: String?,
outputPointer: UnsafeMutablePointer<BridgedStringRef>
) -> Int {
guard let expandedSource = expandedSource else {
outputPointer.pointee = BridgedStringRef()
return -1
}
outputPointer.pointee = allocateBridgedString(expandedSource)
return 0
}
@_cdecl("swift_Macros_expandFreestandingMacro")
@usableFromInline
func expandFreestandingMacro(
diagEnginePtr: UnsafeMutableRawPointer,
macroPtr: UnsafeRawPointer,
discriminatorText: UnsafePointer<CChar>,
rawMacroRole: UInt8,
sourceFilePtr: UnsafeRawPointer,
sourceLocationPtr: UnsafePointer<UInt8>?,
expandedSourceOutPtr: UnsafeMutablePointer<BridgedStringRef>
) -> Int {
// We didn't expand anything so far.
assert(expandedSourceOutPtr.pointee.isEmpty)
guard let sourceLocationPtr = sourceLocationPtr else {
print("NULL source location")
return -1
}
let sourceFilePtr = sourceFilePtr.bindMemory(to: ExportedSourceFile.self, capacity: 1)
guard
let macroSyntax = findSyntaxNodeInSourceFile(
sourceFilePtr: sourceFilePtr,
sourceLocationPtr: sourceLocationPtr,
type: Syntax.self
)
else {
return 1
}
guard
let expansion = macroSyntax.asProtocol(
FreestandingMacroExpansionSyntax.self
)
else {
print("not on a macro expansion node: \(macroSyntax.debugDescription)")
return 1
}
let discriminator = String(cString: discriminatorText)
let macroRole = MacroRole(rawMacroRole: rawMacroRole)
let expandedSource: String? = expandFreestandingMacroImpl(
macroPtr: macroPtr,
macroRole: macroRole,
diagEnginePtr: diagEnginePtr,
expansionSyntax: expansion,
sourceFilePtr: sourceFilePtr,
discriminator: discriminator
)
return makeExpansionOutputResult(
expandedSource: expandedSource,
outputPointer: expandedSourceOutPtr
)
}
func expandFreestandingMacroImpl(
macroPtr: UnsafeRawPointer,
macroRole: MacroRole,
diagEnginePtr: UnsafeMutableRawPointer,
expansionSyntax: FreestandingMacroExpansionSyntax,
sourceFilePtr: UnsafePointer<ExportedSourceFile>,
discriminator: String
) -> String? {
let macroName: String
if let exprSyntax = expansionSyntax.as(MacroExpansionExprSyntax.self) {
macroName = exprSyntax.macroName.text
} else if let declSyntax = expansionSyntax.as(MacroExpansionDeclSyntax.self) {
macroName = declSyntax.macroName.text
} else {
fatalError("unknown syntax")
}
let macro = macroPtr.assumingMemoryBound(to: ExportedExternalMacro.self).pointee
// Map the macro role.
let pluginMacroRole: PluginMessage.MacroRole
switch macroRole {
case .accessor, .member, .memberAttribute, .peer, .conformance, .extension, .preamble, .body:
preconditionFailure("unhandled macro role for freestanding macro")
case .expression: pluginMacroRole = .expression
case .declaration: pluginMacroRole = .declaration
case .codeItem: pluginMacroRole = .codeItem
}
// Send the message.
let message = HostToPluginMessage.expandFreestandingMacro(
macro: .init(moduleName: macro.moduleName, typeName: macro.typeName, name: macroName),
macroRole: pluginMacroRole,
discriminator: discriminator,
syntax: PluginMessage.Syntax(syntax: Syntax(expansionSyntax), in: sourceFilePtr)!,
lexicalContext: pluginLexicalContext(of: expansionSyntax)
)
do {
let result = try macro.plugin.sendMessageAndWait(message)
let expandedSource: String?
let diagnostics: [PluginMessage.Diagnostic]
switch result {
case .expandMacroResult(let _expandedSource, let _diagnostics),
.expandFreestandingMacroResult(let _expandedSource, let _diagnostics):
expandedSource = _expandedSource
diagnostics = _diagnostics
default:
throw PluginError.invalidReponseKind
}
// Process the result.
if !diagnostics.isEmpty {
let diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: diagEnginePtr)
diagEngine.add(exportedSourceFile: sourceFilePtr)
diagEngine.emit(diagnostics, messageSuffix: " (from macro '\(macroName)')")
}
return expandedSource
} catch let error {
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
srcMgr.insert(sourceFilePtr)
srcMgr.diagnose(
diagnostic: .init(
node: Syntax(expansionSyntax),
// FIXME: This is probably a plugin communication error.
// The error might not be relevant as the diagnostic message.
message: ASTGenMacroDiagnostic.thrownError(error)
),
messageSuffix: " (from macro '\(macroName)')"
)
return nil
}
}
@_cdecl("swift_Macros_expandAttachedMacro")
@usableFromInline
func expandAttachedMacro(
diagEnginePtr: UnsafeMutableRawPointer,
macroPtr: UnsafeRawPointer,
discriminatorText: UnsafePointer<CChar>,
qualifiedTypeText: UnsafePointer<CChar>,
conformanceListText: UnsafePointer<CChar>,
rawMacroRole: UInt8,
customAttrSourceFilePtr: UnsafeRawPointer,
customAttrSourceLocPointer: UnsafePointer<UInt8>?,
declarationSourceFilePtr: UnsafeRawPointer,
attachedTo declarationSourceLocPointer: UnsafePointer<UInt8>?,
parentDeclSourceFilePtr: UnsafeRawPointer?,
parentDeclSourceLocPointer: UnsafePointer<UInt8>?,
expandedSourceOutPtr: UnsafeMutablePointer<BridgedStringRef>
) -> Int {
// We didn't expand anything so far.
assert(expandedSourceOutPtr.pointee.isEmpty)
// Dig out the custom attribute for the attached macro declarations.
guard
let customAttrNode = findSyntaxNodeInSourceFile(
sourceFilePtr: customAttrSourceFilePtr,
sourceLocationPtr: customAttrSourceLocPointer,
type: AttributeSyntax.self
)
else {
return 1
}
// Dig out the node for the closure or declaration to which the custom
// attribute is attached.
let node = findSyntaxNodeInSourceFile(
sourceFilePtr: declarationSourceFilePtr,
sourceLocationPtr: declarationSourceLocPointer,
where: { $0.is(DeclSyntax.self) || $0.is(ClosureExprSyntax.self) }
)
guard let node else {
return 1
}
var parentDeclNode: DeclSyntax?
if let parentDeclSourceFilePtr = parentDeclSourceFilePtr {
parentDeclNode = findSyntaxNodeInSourceFile(
sourceFilePtr: parentDeclSourceFilePtr,
sourceLocationPtr: parentDeclSourceLocPointer,
type: DeclSyntax.self
)
}
let customAttrSourceFilePtr = customAttrSourceFilePtr.bindMemory(to: ExportedSourceFile.self, capacity: 1)
let declarationSourceFilePtr = declarationSourceFilePtr.bindMemory(to: ExportedSourceFile.self, capacity: 1)
let parentDeclSourceFilePtr = parentDeclSourceFilePtr?.bindMemory(to: ExportedSourceFile.self, capacity: 1)
let discriminator = String(cString: discriminatorText)
let qualifiedType = String(cString: qualifiedTypeText)
let conformanceList = String(cString: conformanceListText)
let expandedSource: String? = expandAttachedMacroImpl(
diagEnginePtr: diagEnginePtr,
macroPtr: macroPtr,
rawMacroRole: rawMacroRole,
discriminator: discriminator,
qualifiedType: qualifiedType,
conformanceList: conformanceList,
customAttrSourceFilePtr: customAttrSourceFilePtr,
customAttrNode: customAttrNode,
declarationSourceFilePtr: declarationSourceFilePtr,
attachedTo: node,
parentDeclSourceFilePtr: parentDeclSourceFilePtr,
parentDeclNode: parentDeclNode
)
return makeExpansionOutputResult(
expandedSource: expandedSource,
outputPointer: expandedSourceOutPtr
)
}
/// Produce the full lexical context of the given node to pass along to
/// macro expansion.
private func lexicalContext(of node: some SyntaxProtocol) -> [Syntax] {
// FIXME: Should we query the source manager to get the macro expansion
// information?
node.allMacroLexicalContexts()
}
/// Produce the full lexical context of the given node to pass along to
/// macro expansion.
private func pluginLexicalContext(of node: some SyntaxProtocol) -> [PluginMessage.Syntax] {
lexicalContext(of: node).compactMap { .init(syntax: $0) }
}
func expandAttachedMacroImpl(
diagEnginePtr: UnsafeMutableRawPointer,
macroPtr: UnsafeRawPointer,
rawMacroRole: UInt8,
discriminator: String,
qualifiedType: String,
conformanceList: String,
customAttrSourceFilePtr: UnsafePointer<ExportedSourceFile>,
customAttrNode: AttributeSyntax,
declarationSourceFilePtr: UnsafePointer<ExportedSourceFile>,
attachedTo declarationNode: Syntax,
parentDeclSourceFilePtr: UnsafePointer<ExportedSourceFile>?,
parentDeclNode: DeclSyntax?
) -> String? {
let macroName: String = customAttrNode.attributeName.description
let macro = macroPtr.assumingMemoryBound(to: ExportedExternalMacro.self).pointee
// Map the macro role.
let macroRole: PluginMessage.MacroRole
switch MacroRole(rawMacroRole: rawMacroRole) {
case .accessor: macroRole = .accessor
case .member: macroRole = .member
case .memberAttribute: macroRole = .memberAttribute
case .peer: macroRole = .peer
case .conformance: macroRole = .conformance
case .extension: macroRole = .`extension`
case .preamble: macroRole = .preamble
case .body: macroRole = .body
case .expression,
.declaration,
.codeItem:
preconditionFailure("unhandled macro role for attached macro")
}
// Prepare syntax nodes to transfer.
let customAttributeSyntax = PluginMessage.Syntax(
syntax: Syntax(customAttrNode),
in: customAttrSourceFilePtr
)!
let declSyntax = PluginMessage.Syntax(
syntax: declarationNode,
in: declarationSourceFilePtr
)!
let parentDeclSyntax: PluginMessage.Syntax?
if parentDeclNode != nil {
parentDeclSyntax = .init(syntax: Syntax(parentDeclNode!), in: parentDeclSourceFilePtr!)!
} else {
parentDeclSyntax = nil
}
let extendedTypeSyntax: PluginMessage.Syntax?
if (!qualifiedType.isEmpty) {
let typeSyntax: TypeSyntax = "\(raw: qualifiedType)"
extendedTypeSyntax = .init(syntax: Syntax(typeSyntax))!
} else {
extendedTypeSyntax = nil
}
let conformanceListSyntax: PluginMessage.Syntax?
if (conformanceList.isEmpty) {
conformanceListSyntax = nil
} else {
let placeholderDecl: DeclSyntax =
"""
struct Placeholder: \(raw: conformanceList) {}
"""
conformanceListSyntax = .init(syntax: Syntax(placeholderDecl))!
}
// Send the message.
let message = HostToPluginMessage.expandAttachedMacro(
macro: .init(moduleName: macro.moduleName, typeName: macro.typeName, name: macroName),
macroRole: macroRole,
discriminator: discriminator,
attributeSyntax: customAttributeSyntax,
declSyntax: declSyntax,
parentDeclSyntax: parentDeclSyntax,
extendedTypeSyntax: extendedTypeSyntax,
conformanceListSyntax: conformanceListSyntax,
lexicalContext: pluginLexicalContext(of: declarationNode)
)
do {
let expandedSource: String?
let diagnostics: [PluginMessage.Diagnostic]
switch try macro.plugin.sendMessageAndWait(message) {
case .expandMacroResult(let _expandedSource, let _diagnostics):
expandedSource = _expandedSource
diagnostics = _diagnostics
// Handle legacy result message.
case .expandAttachedMacroResult(let _expandedSources, let _diagnostics):
if let _expandedSources = _expandedSources {
expandedSource = SwiftSyntaxMacroExpansion.collapse(
expansions: _expandedSources,
for: MacroRole(rawMacroRole: rawMacroRole),
attachedTo: declarationNode
)
} else {
expandedSource = nil
}
diagnostics = _diagnostics
break
default:
throw PluginError.invalidReponseKind
}
// Process the result.
if !diagnostics.isEmpty {
let diagEngine = PluginDiagnosticsEngine(cxxDiagnosticEngine: diagEnginePtr)
diagEngine.add(exportedSourceFile: customAttrSourceFilePtr)
diagEngine.add(exportedSourceFile: declarationSourceFilePtr)
if let parentDeclSourceFilePtr = parentDeclSourceFilePtr {
diagEngine.add(exportedSourceFile: parentDeclSourceFilePtr)
}
diagEngine.emit(diagnostics, messageSuffix: " (from macro '\(macroName)')")
}
return expandedSource
} catch let error {
let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr)
srcMgr.insert(customAttrSourceFilePtr)
srcMgr.insert(declarationSourceFilePtr)
if let parentDeclSourceFilePtr = parentDeclSourceFilePtr {
srcMgr.insert(parentDeclSourceFilePtr)
}
// FIXME: Need to decide where to diagnose the error:
srcMgr.diagnose(
diagnostic: .init(
node: Syntax(declarationNode),
// FIXME: This is probably a plugin communication error.
// The error might not be relevant as the diagnostic message.
message: ASTGenMacroDiagnostic.thrownError(error)
),
messageSuffix: " (from macro '\(macroName)')"
)
return nil
}
}