Files
swift-mirror/test/Macros/Inputs/syntax_macro_definitions.swift
Doug Gregor 200f2340d9 [Macros] Be deliberate about walking macro arguments vs. expansions
Provide ASTWalker with a customization point to specify whether to
check macro arguments (which are type checked but never emitted), the
macro expansion (which is the result of applying the macro and is
actually emitted into the source), or both. Provide answers for the
~115 different ASTWalker visitors throughout the code base.

Fixes rdar://104042945, which concerns checking of effects in
macro arguments---which we shouldn't do.
2023-02-28 17:48:23 -08:00

1074 lines
29 KiB
Swift

import SwiftDiagnostics
import SwiftOperators
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
/// Replace the label of the first element in the tuple with the given
/// new label.
private func replaceFirstLabel(
of tuple: TupleExprElementListSyntax, with newLabel: String
) -> TupleExprElementListSyntax{
guard let firstElement = tuple.first else {
return tuple
}
return tuple.replacing(
childAt: 0, with: firstElement.with(\.label, .identifier(newLabel)))
}
public struct ColorLiteralMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
let argList = replaceFirstLabel(
of: macro.argumentList, with: "_colorLiteralRed"
)
let initSyntax: ExprSyntax = ".init(\(argList))"
if let leadingTrivia = macro.leadingTrivia {
return initSyntax.with(\.leadingTrivia, leadingTrivia)
}
return initSyntax
}
}
public struct FileIDMacro: ExpressionMacro {
public static func expansion<
Node: FreestandingMacroExpansionSyntax,
Context: MacroExpansionContext
>(
of macro: Node,
in context: Context
) throws -> ExprSyntax {
guard let sourceLoc = context.location(of: macro),
let fileID = sourceLoc.file
else {
throw CustomError.message("can't find location for macro")
}
let fileLiteral: ExprSyntax = "\(literal: fileID)"
if let leadingTrivia = macro.leadingTrivia {
return fileLiteral.with(\.leadingTrivia, leadingTrivia)
}
return fileLiteral
}
}
public struct StringifyMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
guard let argument = macro.argumentList.first?.expression else {
fatalError("boom")
}
return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))"
}
}
public struct StringifyAndTryMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
guard let argument = macro.argumentList.first?.expression else {
fatalError("boom")
}
return "(try \(argument), \(StringLiteralExprSyntax(content: argument.description)))"
}
}
struct SimpleDiagnosticMessage: DiagnosticMessage {
let message: String
let diagnosticID: MessageID
let severity: DiagnosticSeverity
}
extension SimpleDiagnosticMessage: FixItMessage {
var fixItID: MessageID { diagnosticID }
}
public enum AddBlocker: ExpressionMacro {
class AddVisitor: SyntaxRewriter {
var diagnostics: [Diagnostic] = []
override func visit(
_ node: InfixOperatorExprSyntax
) -> ExprSyntax {
if let binOp = node.operatorOperand.as(BinaryOperatorExprSyntax.self) {
if binOp.operatorToken.text == "+" {
let messageID = MessageID(domain: "silly", id: "addblock")
diagnostics.append(
Diagnostic(
node: Syntax(node.operatorOperand),
message: SimpleDiagnosticMessage(
message: "blocked an add; did you mean to subtract?",
diagnosticID: messageID,
severity: .error
),
highlights: [
Syntax(node.leftOperand),
Syntax(node.rightOperand)
],
fixIts: [
FixIt(
message: SimpleDiagnosticMessage(
message: "use '-'",
diagnosticID: messageID,
severity: .error
),
changes: [
FixIt.Change.replace(
oldNode: Syntax(binOp.operatorToken),
newNode: Syntax(
TokenSyntax(
.binaryOperator("-"),
leadingTrivia: binOp.operatorToken.leadingTrivia,
trailingTrivia: binOp.operatorToken.trailingTrivia,
presence: .present
)
)
)
]
),
]
)
)
return ExprSyntax(
node.with(
\.operatorOperand,
ExprSyntax(
binOp.with(
\.operatorToken,
binOp.operatorToken.withKind(.binaryOperator("-"))
)
)
)
)
}
}
return ExprSyntax(node)
}
}
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
let visitor = AddVisitor()
let result = visitor.visit(Syntax(node))
for diag in visitor.diagnostics {
context.diagnose(diag)
}
return result.asProtocol(FreestandingMacroExpansionSyntax.self)!.argumentList.first!.expression
}
}
public class RecursiveMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
guard let argument = macro.argumentList.first?.expression,
argument.description == "false" else {
return "\(macro)"
}
return "()"
}
}
public class NestedDeclInExprMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
return """
{ () -> Void in
struct Foo { }
return ()
}
"""
}
}
enum CustomError: Error, CustomStringConvertible {
case message(String)
var description: String {
switch self {
case .message(let text):
return text
}
}
}
public struct DefineBitwidthNumberedStructsMacro: DeclarationMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let firstElement = node.argumentList.first,
let stringLiteral = firstElement.expression.as(StringLiteralExprSyntax.self),
stringLiteral.segments.count == 1,
case let .stringSegment(prefix) = stringLiteral.segments.first else {
throw CustomError.message("#bitwidthNumberedStructs macro requires a string literal")
}
if prefix.content.text == "BUG" {
return [
"""
struct \(raw: prefix) {
func \(context.createUniqueName("method"))() { return 0 }
func \(context.createUniqueName("method"))() { return 1 }
}
"""
]
}
return [8, 16, 32, 64].map { bitwidth in
"""
struct \(raw: prefix)\(raw: String(bitwidth)) {
func \(context.createUniqueName("method"))() { }
func \(context.createUniqueName("method"))() { }
}
"""
}
}
}
public struct DefineDeclsWithKnownNamesMacro: DeclarationMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
return [
"""
struct A {
func \(context.createUniqueName("method"))() { }
func \(context.createUniqueName("method"))() { }
}
""",
"""
struct B {
func \(context.createUniqueName("method"))() { }
func \(context.createUniqueName("method"))() { }
}
""",
"""
var foo: Int {
1
}
""",
"""
var addOne: (Int) -> Int { { $0 + 1 } }
"""
// FIXME:
// 1. Stored properties are not visited in IRGen
// let addTwo: (Int) -> Int = { $0 + 2 }
// 2. Curry thunk at call sites
// func foo()
// Foo2.foo // AutoClosureExpr with invalid discriminator
]
}
}
public struct WarningMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
guard let firstElement = macro.argumentList.first,
let stringLiteral = firstElement.expression
.as(StringLiteralExprSyntax.self),
stringLiteral.segments.count == 1,
case let .stringSegment(messageString)? = stringLiteral.segments.first
else {
throw CustomError.message("#myWarning macro requires a string literal")
}
context.diagnose(
Diagnostic(
node: Syntax(macro),
message: SimpleDiagnosticMessage(
message: messageString.content.description,
diagnosticID: MessageID(domain: "test", id: "error"),
severity: .warning
)
)
)
return "()"
}
}
public struct PropertyWrapperMacro {}
extension PropertyWrapperMacro: AccessorMacro, Macro {
public static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AccessorDeclSyntax] {
guard let varDecl = declaration.as(VariableDeclSyntax.self),
let binding = varDecl.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
binding.accessor == nil
else {
return []
}
return [
"""
get {
_\(identifier).wrappedValue
}
""",
"""
set {
_\(identifier).wrappedValue = newValue
}
""",
]
}
}
public struct WrapAllProperties: MemberAttributeMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo parent: some DeclGroupSyntax,
providingAttributesFor member: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AttributeSyntax] {
guard member.is(VariableDeclSyntax.self) else {
return []
}
let wrapperTypeName: String
if parent.is(ClassDeclSyntax.self) {
wrapperTypeName = "EnclosingSelfWrapper"
} else {
wrapperTypeName = "Wrapper"
}
let propertyWrapperAttr = AttributeSyntax(
attributeName: SimpleTypeIdentifierSyntax(
name: .identifier(wrapperTypeName)
)
)
return [propertyWrapperAttr]
}
}
public struct TypeWrapperMacro {}
extension TypeWrapperMacro: MemberAttributeMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo decl: some DeclGroupSyntax,
providingAttributesFor member: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AttributeSyntax] {
guard let varDecl = member.as(VariableDeclSyntax.self),
let binding = varDecl.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
binding.accessor == nil
else {
return []
}
if identifier.text == "_storage" {
return []
}
let customAttr = AttributeSyntax(
attributeName: SimpleTypeIdentifierSyntax(
name: .identifier("accessViaStorage")
)
)
return [customAttr]
}
}
extension TypeWrapperMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf decl: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
let storageVariable: DeclSyntax =
"""
private var _storage = _Storage()
"""
return [
storageVariable,
]
}
}
public struct AccessViaStorageMacro: AccessorMacro {
public static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AccessorDeclSyntax] {
guard let varDecl = declaration.as(VariableDeclSyntax.self),
let binding = varDecl.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
binding.accessor == nil
else {
return []
}
if identifier.text == "_storage" {
return []
}
return [
"get { _storage.\(identifier) }",
"set { _storage.\(identifier) = newValue }",
]
}
}
public struct AddMembers: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf decl: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
let storageStruct: DeclSyntax =
"""
struct Storage {}
"""
let storageVariable: DeclSyntax =
"""
private var storage = Storage()
"""
let instanceMethod: DeclSyntax =
"""
func getStorage() -> Storage {
print("synthesized method")
return storage
}
"""
let staticMethod: DeclSyntax =
"""
static func method() {}
"""
let initDecl: DeclSyntax =
"""
init() {}
"""
return [
storageStruct,
storageVariable,
instanceMethod,
staticMethod,
initDecl,
]
}
}
/// Implementation of the `wrapStoredProperties` macro, which can be
/// used to apply an attribute to all of the stored properties of a type.
///
/// This macro demonstrates member-attribute macros.
public struct WrapStoredPropertiesMacro: MemberAttributeMacro {
public static func expansion<
Declaration: DeclGroupSyntax,
Context: MacroExpansionContext
>(
of node: AttributeSyntax,
attachedTo decl: Declaration,
providingAttributesFor member: some DeclSyntaxProtocol,
in context: Context
) throws -> [AttributeSyntax] {
guard let property = member.as(VariableDeclSyntax.self),
property.isStoredProperty
else {
return []
}
guard case let .argumentList(arguments) = node.argument,
let firstElement = arguments.first,
let stringLiteral = firstElement.expression
.as(StringLiteralExprSyntax.self),
stringLiteral.segments.count == 1,
case let .stringSegment(wrapperName)? = stringLiteral.segments.first else {
throw CustomError.message("macro requires a string literal containing the name of an attribute")
}
return [
AttributeSyntax(
attributeName: SimpleTypeIdentifierSyntax(
name: .identifier(wrapperName.content.text)
)
)
.with(\.leadingTrivia, [.newlines(1), .spaces(2)])
]
}
}
extension VariableDeclSyntax {
/// Determine whether this variable has the syntax of a stored property.
///
/// This syntactic check cannot account for semantic adjustments due to,
/// e.g., accessor macros or property wrappers.
var isStoredProperty: Bool {
if bindings.count != 1 {
return false
}
let binding = bindings.first!
switch binding.accessor {
case .none:
return true
case .accessors(let node):
for accessor in node.accessors {
switch accessor.accessorKind.tokenKind {
case .keyword(.willSet), .keyword(.didSet):
// Observers can occur on a stored property.
break
default:
// Other accessors make it a computed property.
return false
}
}
return true
case .getter:
return false
@unknown default:
return false
}
}
}
extension DeclGroupSyntax {
/// Enumerate the stored properties that syntactically occur in this
/// declaration.
func storedProperties() -> [VariableDeclSyntax] {
return members.members.compactMap { member in
guard let variable = member.decl.as(VariableDeclSyntax.self),
variable.isStoredProperty else {
return nil
}
return variable
}
}
}
public enum LeftHandOperandFinderMacro: ExpressionMacro {
class Visitor<Context: MacroExpansionContext>: SyntaxVisitor {
let context: Context
init(context: Context) {
self.context = context
super.init(viewMode: .sourceAccurate)
}
override func visit(
_ node: InfixOperatorExprSyntax
) -> SyntaxVisitorContinueKind {
guard let lhsStartLoc = context.location(of: node.leftOperand),
let lhsEndLoc = context.location(
of: node.leftOperand, at: .beforeTrailingTrivia, filePathMode: .fileID
) else {
fatalError("missing source location information")
}
print("Source range for LHS is \(lhsStartLoc.file!): \(lhsStartLoc.line!):\(lhsStartLoc.column!)-\(lhsEndLoc.line!):\(lhsEndLoc.column!)")
return .visitChildren
}
}
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
let visitor = Visitor(context: context)
visitor.walk(node)
return node.argumentList.first!.expression
}
}
public struct AddCompletionHandler: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Only on functions at the moment. We could handle initializers as well
// with a bit of work.
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
throw CustomError.message("@addCompletionHandler only works on functions")
}
// This only makes sense for async functions.
if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil {
throw CustomError.message(
"@addCompletionHandler requires an async function"
)
}
// Form the completion handler parameter.
let resultType: TypeSyntax? = funcDecl.signature.output?.returnType.with(\.leadingTrivia, []).with(\.trailingTrivia, [])
let completionHandlerParam =
FunctionParameterSyntax(
firstName: .identifier("completionHandler"),
colon: .colonToken(trailingTrivia: .space),
type: "@escaping (\(resultType ?? "")) -> Void" as TypeSyntax
)
// Add the completion handler parameter to the parameter list.
let parameterList = funcDecl.signature.input.parameterList
let newParameterList: FunctionParameterListSyntax
if let lastParam = parameterList.last {
// We need to add a trailing comma to the preceding list.
newParameterList = parameterList.removingLast()
.appending(
lastParam.with(
\.trailingComma,
.commaToken(trailingTrivia: .space)
)
)
.appending(completionHandlerParam)
} else {
newParameterList = parameterList.appending(completionHandlerParam)
}
let callArguments: [String] = try parameterList.map { param in
guard let argName = param.secondName ?? param.firstName else {
throw CustomError.message(
"@addCompletionHandler argument must have a name"
)
}
if let paramName = param.firstName, paramName.text != "_" {
return "\(paramName.text): \(argName.text)"
}
return "\(argName.text)"
}
let call: ExprSyntax =
"\(funcDecl.identifier)(\(raw: callArguments.joined(separator: ", ")))"
// FIXME: We should make CodeBlockSyntax ExpressibleByStringInterpolation,
// so that the full body could go here.
let newBody: ExprSyntax =
"""
Task {
completionHandler(await \(call))
}
"""
// Drop the @addCompletionHandler attribute from the new declaration.
let newAttributeList = AttributeListSyntax(
funcDecl.attributes?.filter {
guard case let .attribute(attribute) = $0,
let attributeType = attribute.attributeName.as(SimpleTypeIdentifierSyntax.self),
let nodeType = node.attributeName.as(SimpleTypeIdentifierSyntax.self)
else {
return true
}
return attributeType.name.text != nodeType.name.text
} ?? []
)
let newFunc =
funcDecl
.with(
\.signature,
funcDecl.signature
.with(
\.effectSpecifiers,
funcDecl.signature.effectSpecifiers?.with(\.asyncSpecifier, nil) // drop async
)
.with(\.output, nil) // drop result type
.with(
\.input, // add completion handler parameter
funcDecl.signature.input.with(\.parameterList, newParameterList)
.with(\.trailingTrivia, [])
)
)
.with(
\.body,
CodeBlockSyntax(
leftBrace: .leftBraceToken(leadingTrivia: .space),
statements: CodeBlockItemListSyntax(
[CodeBlockItemSyntax(item: .expr(newBody))]
),
rightBrace: .rightBraceToken(leadingTrivia: .newline)
)
)
.with(\.attributes, newAttributeList)
.with(\.leadingTrivia, .newlines(2))
return [DeclSyntax(newFunc)]
}
}
public struct WrapInType: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
throw CustomError.message("@wrapInType only applies to functions")
}
// Build a new function with the same signature that forwards arguments
// to the the original function.
let parameterList = funcDecl.signature.input.parameterList
let callArguments: [String] = try parameterList.map { param in
guard let argName = param.secondName ?? param.firstName else {
throw CustomError.message("@wrapInType argument must have a name")
}
if let paramName = param.firstName, paramName.text != "_" {
return "\(paramName.text): \(argName.text)"
}
return "\(argName.text)"
}
let call: ExprSyntax =
"""
\(funcDecl.identifier)(\(raw: callArguments.joined(separator: ", ")))
"""
// Drop the peer macro attribute from the new declaration.
let newAttributeList = AttributeListSyntax(
funcDecl.attributes?.filter {
guard case let .attribute(attribute) = $0,
let attributeType = attribute.attributeName.as(SimpleTypeIdentifierSyntax.self),
let nodeType = node.attributeName.as(SimpleTypeIdentifierSyntax.self)
else {
return true
}
return attributeType.name.text != nodeType.name.text
} ?? []
)
let method =
funcDecl
.with(
\.identifier,
"\(context.createUniqueName(funcDecl.identifier.text))"
)
.with(
\.signature,
funcDecl.signature
)
.with(
\.body,
CodeBlockSyntax(
leftBrace: .leftBraceToken(leadingTrivia: .space),
statements: CodeBlockItemListSyntax(
[CodeBlockItemSyntax(item: .expr(call))]
)
.with(\.leadingTrivia, [.newlines(1), .spaces(2)]),
rightBrace: .rightBraceToken(leadingTrivia: .newline)
)
)
.with(\.attributes, newAttributeList)
let structType: DeclSyntax =
"""
struct \(context.createUniqueName(funcDecl.identifier.text)) {
\(method)
}
"""
return [structType]
}
}
private extension DeclSyntaxProtocol {
var isObservableStoredProperty: Bool {
if let property = self.as(VariableDeclSyntax.self),
let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
identifier.text != "_registrar", identifier.text != "_storage",
binding.accessor == nil {
return true
}
return false
}
}
public struct ObservableMacro: MemberMacro, MemberAttributeMacro {
// MARK: - MemberMacro
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let identified = declaration.asProtocol(IdentifiedDeclSyntax.self) else {
return []
}
let parentName = identified.identifier
let registrar: DeclSyntax =
"""
let _registrar = ObservationRegistrar<\(parentName)>()
"""
let addObserver: DeclSyntax =
"""
public nonisolated func addObserver(_ observer: some Observer<\(parentName)>) {
_registrar.addObserver(observer)
}
"""
let removeObserver: DeclSyntax =
"""
public nonisolated func removeObserver(_ observer: some Observer<\(parentName)>) {
_registrar.removeObserver(observer)
}
"""
let withTransaction: DeclSyntax =
"""
private func withTransaction<T>(_ apply: () throws -> T) rethrows -> T {
_registrar.beginAccess()
defer { _registrar.endAccess() }
return try apply()
}
"""
let memberList = MemberDeclListSyntax(
declaration.members.members.filter {
$0.decl.isObservableStoredProperty
}
)
let storageStruct: DeclSyntax =
"""
private struct Storage {
\(memberList)
}
"""
let storage: DeclSyntax =
"""
private var _storage = Storage()
"""
return [
registrar,
addObserver,
removeObserver,
withTransaction,
storageStruct,
storage,
]
}
// MARK: - MemberAttributeMacro
public static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingAttributesFor member: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [SwiftSyntax.AttributeSyntax] {
guard member.isObservableStoredProperty else {
return []
}
return [
AttributeSyntax(
attributeName: SimpleTypeIdentifierSyntax(
name: .identifier("ObservableProperty")
)
)
]
}
}
public struct ObservablePropertyMacro: AccessorMacro {
public static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AccessorDeclSyntax] {
guard let property = declaration.as(VariableDeclSyntax.self),
let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
binding.accessor == nil
else {
return []
}
let getAccessor: AccessorDeclSyntax =
"""
get {
_registrar.beginAccess(\\.\(identifier))
defer { _registrar.endAccess() }
return _storage.\(identifier)
}
"""
let setAccessor: AccessorDeclSyntax =
"""
set {
_registrar.beginAccess(\\.\(identifier))
_registrar.register(observable: self, willSet: \\.\(identifier), to: newValue)
defer {
_registrar.register(observable: self, didSet: \\.\(identifier))
_registrar.endAccess()
}
_storage.\(identifier) = newValue
}
"""
return [getAccessor, setAccessor]
}
}
extension DeclModifierSyntax {
fileprivate var isNeededAccessLevelModifier: Bool {
switch self.name.tokenKind {
case .keyword(.public): return true
default: return false
}
}
}
extension SyntaxStringInterpolation {
fileprivate mutating func appendInterpolation<Node: SyntaxProtocol>(_ node: Node?) {
if let node {
appendInterpolation(node)
}
}
}
public struct NewTypeMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let type = node.attributeName.as(SimpleTypeIdentifierSyntax.self),
let genericArguments = type.genericArgumentClause?.arguments,
genericArguments.count == 1,
let rawType = genericArguments.first
else {
throw CustomError.message(#"@NewType requires the raw type as an argument, in the form "<RawType>"."#)
}
guard let declaration = declaration.as(StructDeclSyntax.self) else {
throw CustomError.message("@NewType can only be applied to a struct declarations.")
}
let access = declaration.modifiers?.first(where: \.isNeededAccessLevelModifier)
return [
"\(access)typealias RawValue = \(rawType)",
"\(access)var rawValue: RawValue",
"\(access)init(_ rawValue: RawValue) { self.rawValue = rawValue }",
]
}
}
public struct EmptyMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf decl: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
return []
}
}
public struct EquatableMacro: ConformanceMacro {
public static func expansion(
of node: AttributeSyntax,
providingConformancesOf decl: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
let protocolName: TypeSyntax = "Equatable"
return [(protocolName, nil)]
}
}
public struct DelegatedConformanceMacro: ConformanceMacro, MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingConformancesOf decl: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
let protocolName: TypeSyntax = "P"
let conformance: DeclSyntax =
"""
extension Placeholder where Element: P {}
"""
guard let extensionDecl = conformance.as(ExtensionDeclSyntax.self) else {
return []
}
return [(protocolName, extensionDecl.genericWhereClause)]
}
public static func expansion(
of node: AttributeSyntax,
providingMembersOf decl: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
let requirement: DeclSyntax =
"""
static func requirement() where Element : P {
Element.requirement()
}
"""
return [requirement]
}
}