mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
1931 lines
51 KiB
Swift
1931 lines
51 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: LabeledExprListSyntax, with newLabel: String
|
|
) -> LabeledExprListSyntax{
|
|
if tuple.isEmpty {
|
|
return tuple
|
|
}
|
|
|
|
return tuple.with(\.[tuple.startIndex].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))"
|
|
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) else {
|
|
throw CustomError.message("can't find location for macro")
|
|
}
|
|
|
|
let fileLiteral: ExprSyntax = "\(sourceLoc.file)"
|
|
return fileLiteral
|
|
}
|
|
}
|
|
|
|
public struct AssertMacro: 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 "assert(\(argument))"
|
|
}
|
|
}
|
|
|
|
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 ExprAndDeclMacro: ExpressionMacro, DeclarationMacro {
|
|
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 static func expansion(
|
|
of macro: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) -> [DeclSyntax] {
|
|
return []
|
|
}
|
|
}
|
|
|
|
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.operator.as(BinaryOperatorExprSyntax.self) {
|
|
if binOp.operator.text == "+" {
|
|
let messageID = MessageID(domain: "silly", id: "addblock")
|
|
diagnostics.append(
|
|
Diagnostic(
|
|
node: Syntax(node.operator),
|
|
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.operator),
|
|
newNode: Syntax(
|
|
TokenSyntax(
|
|
.binaryOperator("-"),
|
|
presence: .present
|
|
)
|
|
)
|
|
)
|
|
]
|
|
),
|
|
]
|
|
)
|
|
)
|
|
|
|
let minusOperator = binOp.with(\.operator.tokenKind, .binaryOperator("-"))
|
|
return ExprSyntax(node.with(\.operator, ExprSyntax(minusOperator)))
|
|
}
|
|
}
|
|
|
|
return ExprSyntax(node)
|
|
}
|
|
}
|
|
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) -> ExprSyntax {
|
|
let visitor = AddVisitor()
|
|
let result = visitor.rewrite(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 EmptyDeclarationMacro: DeclarationMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return []
|
|
}
|
|
}
|
|
|
|
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.makeUniqueName("method"))() { return 0 }
|
|
func \(context.makeUniqueName("method"))() { return 1 }
|
|
}
|
|
"""
|
|
]
|
|
}
|
|
|
|
return [8, 16, 32, 64].map { bitwidth in
|
|
"""
|
|
|
|
struct \(raw: prefix)\(raw: String(bitwidth)) {
|
|
func \(context.makeUniqueName("method"))() { }
|
|
func \(context.makeUniqueName("method"))() { }
|
|
}
|
|
"""
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct DefineDeclsWithKnownNamesMacro: DeclarationMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return [
|
|
"""
|
|
|
|
struct A {
|
|
func \(context.makeUniqueName("method"))() { }
|
|
func \(context.makeUniqueName("method"))() { }
|
|
}
|
|
""",
|
|
"""
|
|
|
|
struct B {
|
|
func \(context.makeUniqueName("method"))() { }
|
|
func \(context.makeUniqueName("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 VarDeclMacro: CodeItemMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [CodeBlockItemSyntax] {
|
|
let name = context.makeUniqueName("fromMacro")
|
|
return [
|
|
"let \(name) = 23",
|
|
"use(\(name))",
|
|
"""
|
|
if true {
|
|
let \(name) = "string"
|
|
use(\(name))
|
|
}
|
|
"""
|
|
]
|
|
}
|
|
}
|
|
|
|
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 ErrorMacro: 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 {
|
|
let errorNode: Syntax
|
|
if let firstElement = macro.argumentList.first {
|
|
errorNode = Syntax(firstElement)
|
|
} else {
|
|
errorNode = Syntax(macro)
|
|
}
|
|
|
|
let messageID = MessageID(domain: "silly", id: "error")
|
|
let diag = Diagnostic(
|
|
node: errorNode,
|
|
message: SimpleDiagnosticMessage(
|
|
message: "#myError macro requires a string literal",
|
|
diagnosticID: messageID,
|
|
severity: .error
|
|
)
|
|
)
|
|
|
|
throw DiagnosticsError(diagnostics: [diag])
|
|
}
|
|
|
|
context.diagnose(
|
|
Diagnostic(
|
|
node: Syntax(macro),
|
|
message: SimpleDiagnosticMessage(
|
|
message: messageString.content.description,
|
|
diagnosticID: MessageID(domain: "test", id: "error"),
|
|
severity: .error
|
|
)
|
|
)
|
|
)
|
|
|
|
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
|
|
else {
|
|
return []
|
|
}
|
|
|
|
return [
|
|
"""
|
|
|
|
get {
|
|
_\(identifier).wrappedValue
|
|
}
|
|
""",
|
|
"""
|
|
|
|
set {
|
|
_\(identifier).wrappedValue = newValue
|
|
}
|
|
""",
|
|
]
|
|
}
|
|
}
|
|
|
|
extension PropertyWrapperMacro: PeerMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingPeersOf declaration: some DeclSyntaxProtocol,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
guard let varDecl = declaration.as(VariableDeclSyntax.self),
|
|
let binding = varDecl.bindings.first,
|
|
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
|
|
binding.accessorBlock == nil,
|
|
let type = binding.typeAnnotation?.type
|
|
else {
|
|
return []
|
|
}
|
|
|
|
return [
|
|
"""
|
|
var _\(raw: identifier.trimmedDescription): MyWrapperThingy<\(type)>
|
|
"""
|
|
]
|
|
}
|
|
}
|
|
|
|
extension AccessorBlockSyntax {
|
|
var hasGetter: Bool {
|
|
switch self.accessors {
|
|
case .accessors(let accessors):
|
|
for accessor in accessors {
|
|
if accessor.accessorSpecifier.text == "get" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
case .getter:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct PropertyWrapperSkipsComputedMacro {}
|
|
|
|
extension PropertyWrapperSkipsComputedMacro: 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.accessorBlock?.hasGetter ?? false)
|
|
else {
|
|
return []
|
|
}
|
|
|
|
return [
|
|
"""
|
|
|
|
get {
|
|
_\(identifier).wrappedValue
|
|
}
|
|
""",
|
|
"""
|
|
|
|
set {
|
|
_\(identifier).wrappedValue = newValue
|
|
}
|
|
""",
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct WillSetMacro: 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 else {
|
|
return []
|
|
}
|
|
|
|
return [
|
|
"""
|
|
willSet { }
|
|
"""
|
|
]
|
|
}
|
|
}
|
|
|
|
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: IdentifierTypeSyntax(
|
|
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.accessorBlock == nil
|
|
else {
|
|
return []
|
|
}
|
|
|
|
if identifier.text == "_storage" {
|
|
return []
|
|
}
|
|
|
|
let customAttr = AttributeSyntax(
|
|
attributeName: IdentifierTypeSyntax(
|
|
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.accessorBlock == 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 uniqueClassName = context.makeUniqueName("uniqueClass")
|
|
|
|
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() { _ = \(uniqueClassName)() }
|
|
"""
|
|
|
|
let classDecl: DeclSyntax =
|
|
"""
|
|
class \(uniqueClassName) { }
|
|
"""
|
|
|
|
return [
|
|
storageStruct,
|
|
storageVariable,
|
|
instanceMethod,
|
|
staticMethod,
|
|
initDecl,
|
|
classDecl,
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct AddExtMembers: MemberMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingMembersOf decl: some DeclGroupSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
let uniqueClassName = context.makeUniqueName("uniqueClass")
|
|
|
|
let instanceMethod: DeclSyntax =
|
|
"""
|
|
func extInstanceMethod() {}
|
|
"""
|
|
|
|
let staticMethod: DeclSyntax =
|
|
"""
|
|
static func extStaticMethod() {}
|
|
"""
|
|
|
|
let classDecl: DeclSyntax =
|
|
"""
|
|
class \(uniqueClassName) { }
|
|
"""
|
|
|
|
return [
|
|
instanceMethod,
|
|
staticMethod,
|
|
classDecl,
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct AddArbitraryMembers: MemberMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingMembersOf decl: some DeclGroupSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
guard let identified = decl.asProtocol(NamedDeclSyntax.self) else {
|
|
return []
|
|
}
|
|
|
|
let parentName = identified.name.trimmed
|
|
return [
|
|
"struct \(parentName)1 {}",
|
|
"struct \(parentName)2 {}",
|
|
"struct \(parentName)3 {}",
|
|
]
|
|
}
|
|
}
|
|
|
|
/// 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.arguments,
|
|
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: IdentifierTypeSyntax(
|
|
name: .identifier(wrapperName.content.text)
|
|
)
|
|
)
|
|
]
|
|
}
|
|
}
|
|
|
|
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.accessorBlock?.accessors {
|
|
case .none:
|
|
return true
|
|
|
|
case .accessors(let node):
|
|
for accessor in node {
|
|
switch accessor.accessorSpecifier.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 memberBlock.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.returnClause?.type.with(\.leadingTrivia, []).with(\.trailingTrivia, [])
|
|
|
|
let completionHandlerParam =
|
|
FunctionParameterSyntax(
|
|
firstName: .identifier("completionHandler"),
|
|
colon: .colonToken(),
|
|
type: "@escaping (\(resultType ?? "")) -> Void" as TypeSyntax
|
|
)
|
|
|
|
// Add the completion handler parameter to the parameter list.
|
|
let parameterList = funcDecl.signature.parameterClause.parameters
|
|
var newParameterList = parameterList
|
|
if !newParameterList.isEmpty {
|
|
// We need to add a trailing comma to the preceding list.
|
|
let lastIndex = newParameterList.index(before: newParameterList.endIndex)
|
|
newParameterList[lastIndex].trailingComma = .commaToken()
|
|
}
|
|
newParameterList.append(completionHandlerParam)
|
|
|
|
let callArguments: [String] = parameterList.map { param in
|
|
let argName = param.secondName ?? param.firstName
|
|
|
|
if param.firstName.text != "_" {
|
|
return "\(param.firstName.text): \(argName.text)"
|
|
}
|
|
|
|
return "\(argName.text)"
|
|
}
|
|
|
|
let call: ExprSyntax =
|
|
"\(funcDecl.name)(\(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 = funcDecl.attributes.filter {
|
|
guard case let .attribute(attribute) = $0,
|
|
let attributeType = attribute.attributeName.as(IdentifierTypeSyntax.self),
|
|
let nodeType = node.attributeName.as(IdentifierTypeSyntax.self)
|
|
else {
|
|
return true
|
|
}
|
|
|
|
return attributeType.name.text != nodeType.name.text
|
|
}
|
|
|
|
var newFunc = funcDecl
|
|
newFunc.signature.effectSpecifiers?.asyncSpecifier = nil // drop async
|
|
newFunc.signature.returnClause = nil // drop result type
|
|
newFunc.signature.parameterClause.parameters = newParameterList
|
|
newFunc.signature.parameterClause.trailingTrivia = []
|
|
newFunc.body = CodeBlockSyntax(
|
|
leftBrace: .leftBraceToken(),
|
|
statements: CodeBlockItemListSyntax(
|
|
[CodeBlockItemSyntax(item: .expr(newBody))]
|
|
),
|
|
rightBrace: .rightBraceToken()
|
|
)
|
|
newFunc.attributes = newAttributeList
|
|
|
|
return [DeclSyntax(newFunc)]
|
|
}
|
|
}
|
|
|
|
public struct InvalidMacro: PeerMacro, DeclarationMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingPeersOf declaration: some DeclSyntaxProtocol,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return [
|
|
"import Swift",
|
|
"precedencegroup MyPrecedence {}",
|
|
"@attached(member) macro myMacro()",
|
|
"extension Int {}",
|
|
"""
|
|
@main
|
|
struct MyMain {
|
|
static func main() {}
|
|
}
|
|
""",
|
|
"typealias Array = Void",
|
|
"typealias Dictionary = Void",
|
|
"typealias BooleanLiteralType = Void",
|
|
"typealias ExtendedGraphemeClusterType = Void",
|
|
"typealias FloatLiteralType = Void",
|
|
"typealias IntegerLiteralType = Void",
|
|
"typealias StringLiteralType = Void",
|
|
"typealias UnicodeScalarType = Void",
|
|
"typealias _ColorLiteralType = Void",
|
|
"typealias _ImageLiteralType = Void",
|
|
"typealias _FileReferenceLiteralType = Void",
|
|
]
|
|
}
|
|
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return [
|
|
"var value: Int"
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct CoerceToIntMacro: ExpressionMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) -> ExprSyntax {
|
|
"\(node.argumentList.first!.expression) as Int"
|
|
}
|
|
}
|
|
|
|
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.parameterClause.parameters
|
|
let callArguments: [String] = parameterList.map { param in
|
|
let argName = param.secondName ?? param.firstName
|
|
|
|
if param.firstName.text != "_" {
|
|
return "\(param.firstName.text): \(argName.text)"
|
|
}
|
|
|
|
return "\(argName.text)"
|
|
}
|
|
|
|
let call: ExprSyntax =
|
|
"""
|
|
\(funcDecl.name)(\(raw: callArguments.joined(separator: ", ")))
|
|
"""
|
|
|
|
// Drop the peer macro attribute from the new declaration.
|
|
let newAttributeList = funcDecl.attributes.filter {
|
|
guard case let .attribute(attribute) = $0,
|
|
let attributeType = attribute.attributeName.as(IdentifierTypeSyntax.self),
|
|
let nodeType = node.attributeName.as(IdentifierTypeSyntax.self)
|
|
else {
|
|
return true
|
|
}
|
|
|
|
return attributeType.name.text != nodeType.name.text
|
|
}
|
|
|
|
var method = funcDecl
|
|
method.name = "\(context.makeUniqueName(funcDecl.name.text))"
|
|
method.signature = funcDecl.signature
|
|
method.body = CodeBlockSyntax(
|
|
leftBrace: .leftBraceToken(),
|
|
statements: CodeBlockItemListSyntax(
|
|
[CodeBlockItemSyntax(item: .expr(call))]
|
|
),
|
|
rightBrace: .rightBraceToken()
|
|
)
|
|
method.attributes = newAttributeList
|
|
|
|
let structType: DeclSyntax =
|
|
"""
|
|
struct \(context.makeUniqueName(funcDecl.name.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.accessorBlock == 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(NamedDeclSyntax.self) else {
|
|
return []
|
|
}
|
|
|
|
let parentName = identified.name
|
|
|
|
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 = declaration.memberBlock.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: IdentifierTypeSyntax(
|
|
name: .identifier("ObservableProperty")
|
|
)
|
|
)
|
|
]
|
|
}
|
|
|
|
}
|
|
|
|
extension ObservableMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo decl: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
if (protocols.isEmpty) {
|
|
return []
|
|
}
|
|
|
|
let decl: DeclSyntax =
|
|
"""
|
|
extension \(raw: type.trimmedDescription): Observable {
|
|
}
|
|
"""
|
|
|
|
return [
|
|
decl.cast(ExtensionDeclSyntax.self)
|
|
]
|
|
}
|
|
}
|
|
|
|
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.accessorBlock == 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(IdentifierTypeSyntax.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 EmptyPeerMacro: PeerMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingPeersOf declaration: some DeclSyntaxProtocol,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return []
|
|
}
|
|
}
|
|
|
|
public struct EquatableMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let ext: DeclSyntax = "extension \(type.trimmed): Equatable {}"
|
|
return [ext.cast(ExtensionDeclSyntax.self)]
|
|
}
|
|
}
|
|
|
|
public struct EquatableViaMembersMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo decl: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let comparisons: [String] = decl.storedProperties().map { property in
|
|
guard let binding = property.bindings.first,
|
|
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else {
|
|
return "true"
|
|
}
|
|
|
|
return "lhs.\(identifier) == rhs.\(identifier)"
|
|
}
|
|
|
|
let condition = comparisons.joined(separator: " && ")
|
|
let equalOperator: DeclSyntax = """
|
|
static func ==(lhs: \(type.trimmed), rhs: \(type.trimmed)) -> Bool {
|
|
return \(raw: condition)
|
|
}
|
|
"""
|
|
|
|
let ext: DeclSyntax = """
|
|
extension \(type.trimmed): Equatable {
|
|
\(equalOperator)
|
|
}
|
|
"""
|
|
return [ext.cast(ExtensionDeclSyntax.self)]
|
|
}
|
|
}
|
|
|
|
public struct ConformanceViaExtensionMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo decl: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
if (protocols.isEmpty) {
|
|
return []
|
|
}
|
|
|
|
let decl: DeclSyntax =
|
|
"""
|
|
extension \(raw: type.trimmedDescription): MyProtocol {
|
|
}
|
|
"""
|
|
|
|
return [
|
|
decl.cast(ExtensionDeclSyntax.self)
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct HashableMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let ext: DeclSyntax = "extension \(type.trimmed): Hashable {}"
|
|
return [ext.cast(ExtensionDeclSyntax.self)]
|
|
}
|
|
}
|
|
|
|
public struct ImpliesHashableMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let ext: DeclSyntax = "extension \(type.trimmed): ImpliesHashable {}"
|
|
return [ext.cast(ExtensionDeclSyntax.self)]
|
|
}
|
|
}
|
|
|
|
public struct DelegatedConformanceMacro: ExtensionMacro, MemberMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let conformance: DeclSyntax =
|
|
"""
|
|
extension \(type.trimmed): P where Element: P {}
|
|
"""
|
|
|
|
guard let extensionDecl = conformance.as(ExtensionDeclSyntax.self) else {
|
|
return []
|
|
}
|
|
|
|
return [extensionDecl]
|
|
}
|
|
|
|
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]
|
|
}
|
|
}
|
|
|
|
public struct DelegatedConformanceViaExtensionMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo decl: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
if (protocols.isEmpty) {
|
|
return []
|
|
}
|
|
|
|
let decl: DeclSyntax =
|
|
"""
|
|
extension \(raw: type.trimmedDescription): P where Element: P {
|
|
static func requirement() {
|
|
Element.requirement()
|
|
}
|
|
}
|
|
|
|
"""
|
|
|
|
guard let extensionDecl = decl.as(ExtensionDeclSyntax.self) else {
|
|
return []
|
|
}
|
|
|
|
return [
|
|
extensionDecl
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct AlwaysAddConformance: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo decl: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let decl: DeclSyntax =
|
|
"""
|
|
extension \(raw: type.trimmedDescription): P where Element: P {
|
|
static func requirement() {
|
|
Element.requirement()
|
|
}
|
|
}
|
|
|
|
"""
|
|
|
|
return [
|
|
decl.cast(ExtensionDeclSyntax.self)
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct ConditionallyAvailableConformance: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo decl: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let decl: DeclSyntax =
|
|
"""
|
|
@available(macOS 99, *)
|
|
extension \(raw: type.trimmedDescription): Equatable {}
|
|
"""
|
|
|
|
return [
|
|
decl.cast(ExtensionDeclSyntax.self)
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct AddAllConformancesMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo decl: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
protocols.map { proto in
|
|
let decl: DeclSyntax =
|
|
"""
|
|
extension \(type): \(proto) {}
|
|
"""
|
|
return decl.cast(ExtensionDeclSyntax.self)
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct AlwaysAddCodable: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo decl: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let decl: DeclSyntax =
|
|
"""
|
|
extension \(raw: type.trimmedDescription): Codable {
|
|
}
|
|
|
|
"""
|
|
|
|
return [
|
|
decl.cast(ExtensionDeclSyntax.self)
|
|
]
|
|
}
|
|
}
|
|
|
|
|
|
public struct ExtendableEnum: MemberMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingMembersOf declaration: some DeclGroupSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
let unknownDecl: DeclSyntax =
|
|
"""
|
|
func unknown() -> Int { 34 } // or something like: `case unknown`
|
|
"""
|
|
return [unknownDecl]
|
|
}
|
|
}
|
|
|
|
public struct DefineStructWithUnqualifiedLookupMacro: DeclarationMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return ["""
|
|
struct StructWithUnqualifiedLookup {
|
|
let hello = 1
|
|
|
|
func foo() -> Int {
|
|
hello + world // looks up "world" in the parent scope
|
|
}
|
|
}
|
|
"""]
|
|
}
|
|
}
|
|
|
|
public struct AddMemberWithFixIt: MemberMacro {
|
|
public static func expansion<
|
|
Declaration: DeclGroupSyntax, Context: MacroExpansionContext
|
|
>(
|
|
of node: AttributeSyntax,
|
|
providingMembersOf declaration: Declaration,
|
|
in context: Context
|
|
) throws -> [DeclSyntax] {
|
|
[
|
|
"""
|
|
func foo() {
|
|
var x = 0
|
|
_ = x
|
|
}
|
|
"""
|
|
]
|
|
}
|
|
}
|
|
|
|
extension LabeledExprListSyntax {
|
|
/// Retrieve the first element with the given label.
|
|
func first(labeled name: String) -> Element? {
|
|
return first { element in
|
|
if let label = element.label, label.text == name {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public struct DefineAnonymousTypesMacro: DeclarationMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
guard let body = node.trailingClosure else {
|
|
throw CustomError.message("#anonymousTypes macro requires a trailing closure")
|
|
}
|
|
|
|
let accessSpecifier: String
|
|
if let _ = node.argumentList.first(labeled: "public") {
|
|
accessSpecifier = "public "
|
|
} else {
|
|
accessSpecifier = ""
|
|
}
|
|
var results: [DeclSyntax] = [
|
|
"""
|
|
|
|
\(raw:accessSpecifier)class \(context.makeUniqueName("name")) {
|
|
\(raw:accessSpecifier)func hello() -> String {
|
|
\(body.statements)
|
|
}
|
|
|
|
\(raw:accessSpecifier)func getSelf() -> Any.Type { return Self.self }
|
|
}
|
|
""",
|
|
"""
|
|
|
|
enum \(context.makeUniqueName("name")) {
|
|
case apple
|
|
case banana
|
|
|
|
func hello() -> String {
|
|
\(body.statements)
|
|
}
|
|
}
|
|
""",
|
|
"""
|
|
|
|
struct \(context.makeUniqueName("name")): Equatable {
|
|
static func == (lhs: Self, rhs: Self) -> Bool { false }
|
|
}
|
|
"""
|
|
]
|
|
|
|
if let _ = node.argumentList.first(labeled: "causeErrors") {
|
|
|
|
results += ["""
|
|
|
|
struct \(context.makeUniqueName("name"))<T> where T == Equatable { // expect error: need 'any'
|
|
#introduceTypeCheckingErrors // make sure we get nested errors
|
|
}
|
|
"""]
|
|
}
|
|
|
|
return results
|
|
}
|
|
}
|
|
|
|
public struct IntroduceTypeCheckingErrorsMacro: DeclarationMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) -> [DeclSyntax] {
|
|
return [
|
|
"""
|
|
|
|
struct \(context.makeUniqueName("name")) {
|
|
struct \(context.makeUniqueName("name"))<T> where T == Hashable { // expect error: need 'any'
|
|
}
|
|
}
|
|
"""
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct AddClassReferencingSelfMacro: PeerMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingPeersOf declaration: some DeclSyntaxProtocol,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
|
|
throw CustomError.message("Macro can only be applied to a protocol declarations.")
|
|
}
|
|
|
|
let className = "\(protocolDecl.name.text)Builder"
|
|
return [
|
|
"""
|
|
struct \(raw: className) {
|
|
init(_ build: (_ builder: Self) -> Self) {
|
|
_ = build(self)
|
|
}
|
|
}
|
|
"""
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct SimpleCodeItemMacro: CodeItemMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [CodeBlockItemSyntax] {
|
|
[
|
|
.init(item: .decl("""
|
|
struct \(context.makeUniqueName("foo")) {
|
|
var x: Int
|
|
}
|
|
""")),
|
|
.init(item: .stmt("""
|
|
if true {
|
|
print("from stmt")
|
|
usedInExpandedStmt()
|
|
}
|
|
""")),
|
|
.init(item: .stmt("""
|
|
if false {
|
|
print("impossible")
|
|
}
|
|
""")),
|
|
.init(item: .expr("""
|
|
print("from expr")
|
|
""")),
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct MultiStatementClosure: ExpressionMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> ExprSyntax {
|
|
return """
|
|
{
|
|
let temp = 10
|
|
let result = temp
|
|
return result
|
|
}()
|
|
"""
|
|
}
|
|
}
|
|
|
|
public struct VarValueMacro: DeclarationMacro, PeerMacro {
|
|
public static func expansion(
|
|
of macro: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) -> [DeclSyntax] {
|
|
return [
|
|
"var value: Int { 1 }"
|
|
]
|
|
}
|
|
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingPeersOf declaration: some DeclSyntaxProtocol,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return [
|
|
"var value: Int { 1 }"
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct SingleMemberMacro: MemberMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingMembersOf decl: some DeclGroupSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
let member: DeclSyntax =
|
|
"""
|
|
var expandedMember: Int = 10
|
|
"""
|
|
|
|
return [
|
|
member,
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct UseIdentifierMacro: DeclarationMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
guard let argument = node.argumentList.first?.expression.as(StringLiteralExprSyntax.self) else {
|
|
fatalError("boom")
|
|
}
|
|
return [
|
|
"""
|
|
func \(context.makeUniqueName("name"))() {
|
|
_ = \(raw: argument.segments.first!)
|
|
}
|
|
""",
|
|
"""
|
|
struct Foo {
|
|
var \(context.makeUniqueName("name")): Int {
|
|
_ = \(raw: argument.segments.first!)
|
|
return 0
|
|
}
|
|
}
|
|
"""
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct StaticFooFuncMacro: DeclarationMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return [
|
|
"static func foo() {}",
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct SelfAlwaysEqualOperator: DeclarationMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return [
|
|
"static func ==(lhs: Self, rhs: Bool) -> Bool { true }",
|
|
]
|
|
}
|
|
}
|
|
|
|
extension SelfAlwaysEqualOperator: MemberMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingMembersOf decl: some DeclGroupSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return [
|
|
"static func ==(lhs: Self, rhs: Bool) -> Bool { true }",
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct AddPeerStoredPropertyMacro: PeerMacro, Sendable {
|
|
public static func expansion(
|
|
of attribute: AttributeSyntax,
|
|
providingPeersOf declaration: some DeclSyntaxProtocol,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
return [
|
|
"""
|
|
|
|
private var _foo: Int = 100
|
|
"""
|
|
]
|
|
}
|
|
}
|
|
|
|
public struct InitializableMacro: ExtensionMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
attachedTo: some DeclGroupSyntax,
|
|
providingExtensionsOf type: some TypeSyntaxProtocol,
|
|
conformingTo protocols: [TypeSyntax],
|
|
in context: some MacroExpansionContext
|
|
) throws -> [ExtensionDeclSyntax] {
|
|
let ext: DeclSyntax =
|
|
"""
|
|
extension \(type.trimmed): Initializable {
|
|
init(value: Int) {}
|
|
}
|
|
"""
|
|
return [ext.cast(ExtensionDeclSyntax.self)]
|
|
}
|
|
}
|
|
|
|
public struct PeerValueWithSuffixNameMacro: PeerMacro {
|
|
public static func expansion(
|
|
of node: AttributeSyntax,
|
|
providingPeersOf declaration: some DeclSyntaxProtocol,
|
|
in context: some MacroExpansionContext
|
|
) throws -> [DeclSyntax] {
|
|
guard let identified = declaration.asProtocol(NamedDeclSyntax.self) else {
|
|
throw CustomError.message("Macro can only be applied to an identified declarations.")
|
|
}
|
|
return ["var \(raw: identified.name.text)_peer: Int { 1 }"]
|
|
}
|
|
}
|
|
|
|
public struct MagicFileMacro: ExpressionMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) -> ExprSyntax {
|
|
return "#file"
|
|
}
|
|
}
|
|
|
|
public struct MagicLineMacro: ExpressionMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) -> ExprSyntax {
|
|
return "(#line)"
|
|
}
|
|
}
|
|
|
|
public struct NestedMagicLiteralMacro: ExpressionMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) -> ExprSyntax {
|
|
return
|
|
"""
|
|
{
|
|
print(#MagicFile)
|
|
print(#MagicLine)
|
|
}()
|
|
"""
|
|
}
|
|
}
|