Files
swift-mirror/lib/ASTGen/Sources/MacroEvaluation/Macros.swift
Doug Gregor 3082b04b75 [Macros] Feed the static build configuration into macro expansions
Thread the static build configuration (formed from language options) in
to the macro plugin handler, which will serialize it for use in the
macro implementation. test this with a simple macro that checks
whether a particular custom configuration (set via `-D`) is enabled or
not.

This required some re-layering, sinking the logic for building a
StaticBuildConfiguration from language options down into a new
swiftBasicSwift library, which sits on top of the C++ swiftBasic and
provides Swift functionality for it. That can be used by the C++
swiftAST to cache the StaticBuildConfiguration on the ASTContext,
making it available for other parts of ASTGen.
2025-09-29 18:42:15 -07:00

801 lines
27 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 SwiftIfConfig
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(
cContext: BridgedASTContext,
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,
cContext: cContext,
expansionSyntax: expansion,
sourceFilePtr: sourceFilePtr,
discriminator: discriminator
)
return makeExpansionOutputResult(
expandedSource: expandedSource,
outputPointer: expandedSourceOutPtr
)
}
func expandFreestandingMacroImpl(
macroPtr: UnsafeRawPointer,
macroRole: MacroRole,
cContext: BridgedASTContext,
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.
do {
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),
staticBuildConfiguration: try cContext.staticBuildConfiguration.asJSON
)
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(cContext: cContext)
diagEngine.add(exportedSourceFile: sourceFilePtr)
diagEngine.emit(diagnostics, messageSuffix: " (from macro '\(macroName)')")
}
return expandedSource
} catch let error {
let srcMgr = SourceManager(cContext: cContext)
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(
cContext: BridgedASTContext,
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(
cContext: cContext,
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(
cContext: BridgedASTContext,
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.
do {
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),
staticBuildConfiguration: try cContext.staticBuildConfiguration.asJSON
)
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(cContext: cContext)
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(cContext: cContext)
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
}
}
extension StaticBuildConfiguration {
/// Form the JSON representation of this static build configuration.
var asJSON: String {
get throws {
try String(decoding: JSON.encode(self), as: UTF8.self)
}
}
}