A macro is a fragment of code which has been given a name. Whenever the name is used, it is replaced by the contents of the macro.
#define BUFFER_SIZE 1024
foo = (char *) malloc (BUFFER_SIZE);
foo = (char *) malloc (1024);
#define lang_init() c_init()
lang_init()
c_init()
#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str (foo)
→ "foo"
xstr (foo)
→ xstr (4)
→ str (4)
→ "4"
#define COMMAND(NAME) { #NAME, NAME ## _command }
COMMAND (quit),
COMMAND (help),
{ "quit", quit_command },
{ "help", help_command },
宣告
class Temp {
let a = 1
}
let a = 1
public struct DeclSyntax:
DeclSyntaxProtocol,
SyntaxHashable {}
public struct ClassDeclSyntax:
DeclSyntaxProtocol,
SyntaxHashable {}
public struct DeclSyntax: DeclSyntaxProtocol, SyntaxHashable {
public init?<S: SyntaxProtocol>(_ node: S) {
switch node.raw.kind {
case .accessorDecl, .actorDecl, .associatedtypeDecl,
.classDecl, .deinitializerDecl, .editorPlaceholderDecl,
.enumCaseDecl, .enumDecl, .extensionDecl,
.functionDecl, .ifConfigDecl, .importDecl, .initializerDecl, .macroDecl, .macroExpansionDecl, .missingDecl, .operatorDecl, .poundSourceLocation, .precedenceGroupDecl, .protocolDecl, .structDecl, .subscriptDecl, .typealiasDecl, .variableDecl:
self._syntaxNode = node._syntaxNode
default:
return nil
}
}
}
extension ActorDeclSyntax: DeclGroupSyntax {}
extension ClassDeclSyntax: DeclGroupSyntax {}
extension EnumDeclSyntax: DeclGroupSyntax {}
extension ExtensionDeclSyntax: DeclGroupSyntax {}
extension ProtocolDeclSyntax: DeclGroupSyntax {}
extension StructDeclSyntax: DeclGroupSyntax {}
表達式 表述式
1 // let a = 1
1 + 2
print("Hello World")
public func expansionDecl() throws -> DeclSyntax {}
public func expansionExpr() throws -> ExprSyntax {}
import SwiftSyntaxBuilder
public func expansionDecl() throws -> DeclSyntax {
// x
// return "1"
return "class Temp {}"
}
public func expansionExpr() throws -> ExprSyntax {
// x
// return "class Temp {}"
return "1"
}
export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-5.9-DEVELOPMENT-SNAPSHOT-2023-06-05-a.xctoolchain/Info.plist)
swift --version
Apple Swift version 5.9-dev (LLVM 464b04eb9b157e3, Swift 7203d52cb1e074d)
Target: x86_64-apple-macosx12.0
Package@swift-5.9.swift
// swift-tools-version: 5.9
import PackageDescription
import CompilerPluginSupport
let package = Package(
name: "MacroPrac",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
.macCatalyst(.v13),
],
dependencies: [
.package (url: "https://github.com/apple/swift-syntax.git", from:
"509.0.0-Swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"),
],
targets: [
.executableTarget(
name: "MacroMain",
dependencies: [
"WWDCMacros",
]),
.macro(
name: "WWDCMacrosPlugin",
dependencies:[
.product(name: "SwiftSyntaxMacros",package:"swift-syntax"),
.product(name: "SwiftCompilerPlugin",package: "swift-syntax"),
]),
.target(
name: "WWDCMacros",
dependencies: [
"WWDCMacrosPlugin",
]),
.testTarget(
name: "Macro123Tests",
dependencies: [
"WWDCMacrosPlugin",
.product(name: "SwiftSyntaxMacros",package:"swift-syntax"),
.product(name: "SwiftSyntaxMacrosTestSupport",package:"swift-syntax"),
]),
])
// swift-tools-version: 5.9
import CompilerPluginSupport
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
.macCatalyst(.v13),
],
dependencies: [
.package (url: "https://github.com/apple/swift-syntax.git", from:
"509.0.0-Swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"),
],
.macro(name: "WWDCMacrosPlugin")
.target(name: "WWDCMacros")
#someFreestanding()
@SomeAttached var a: Int
macro | protocol |
---|---|
@freestanding(expression) | ExpressionMacro |
@freestanding(declaration) | DeclarationMacro |
@attached(peer) | PeerMacro |
@attached(accessor) | AccessorMacro |
@attached(memberAttribute) | MemberAttributeMacro |
@attached(member) | MemberMacro |
@attached(conformance) | ConformanceMacro |
// .macro(name: "WWDCMacrosPlugin")
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
public struct OneMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext)
-> ExprSyntax {
return "1"
}
}
// .macro(name: "WWDCMacrosPlugin")
#if canImport(SwiftCompilerPlugin)
import SwiftCompilerPlugin
import SwiftSyntaxMacros
@main
struct MyMacrosPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
OneMacro.self,
]
}
#endif
// .target(name: "WWDCMacros")
@freestanding(expression)
public macro one() = #externalMacro(
module: "WWDCMacrosPlugin",
type: "OneMacro"
)
import WWDCMacros
let a = #one()
import WWDCMacros
let a = 1
Swift.Error
DiagnosticMessage
Swift.Error
static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
throw MyError.caseA
}
DiagnosticMessage
(1/2)
static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
let error = Diagnostic(
node: attribute,
message: MyLibDiagnostic.someError
)
context.diagnose(structError)
return []
}
DiagnosticMessage
(2/2)
enum MyLibDiagnostic: String, DiagnosticMessage {
case someError
var severity: DiagnosticSeverity { .error }
var message: String {
switch self {
case .someError:
return "someError"
}
}
var diagnosticID: MessageID {
MessageID(domain: "MyLibMacros", id: rawValue)
}
}
import SwiftSyntaxMacrosTestSupport
func testOne() {
assertMacroExpansion(
"#one()",
expandedSource: """
1
""",
macros: ["one": OneMacro.self]
)
}
-> [DeclSyntax]
: Code Gen-> [AttributeSyntax]
: Code Gen@memberAttribute
//@memberAttribute
struct A {
var a: Int
var b: Int
@abc
var c: Int
}
var a: Int
var b: Int
@abc
var c: Int
return ["@abc"]
public struct SomeMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
// ...
)
}
#free<T1, T2>(v1, v2)
#
free
<T1, T2>
(
v1, v2
)
#free {…}
{...}
#free {…} v2: {…}
public struct SomwMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
// ...
)
}
@MyMacro(123, 456)
@
MyMacro
(
123, 456
)
@attached(xxx)
macro test<T>(_ v1: Int = 0, _ v2: T)
@test("a")
syntax.attributeName = "test"
// T ???
syntax.argument[0] = "a"
// macro test<T>(_ v1: Int = 0, _ v2: T)
// @test("a")
let v1, v2: Syntax
if syntax.count == 1 {
v1 = 0
v2 = syntax.argument[0]
} else syntax.count == 2 {
v1 = syntax.argument[0]
v2 = syntax.argument[1]
}
public struct MyExpressionMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext)
-> ExprSyntax {
let a = #one
let a = 1
public struct MyDeclarationMacro: DeclarationMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
#tuple(2)
struct Tuple2<V0, V1> {
let v0: V0
let v1: V1
}
public struct MyCodeItemMacro: CodeItemMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext)
-> [CodeBlockItemSyntax] {
let settings: [SwiftSetting] = [
.enableExperimentalFeature("CodeItemMacros"),
]
.target(
name: "WWDCMacros",
dependencies: [
"WWDCMacrosPlugin",
],
swiftSettings: settings
),
import Foundation
let a = 1
print(a)
CodeBlockItem(Decl(import Foundation))
CodeBlockItem(Decl(let a = 1))
CodeBlockItem(Expr(print(a)))
func test() {
line1
line2
line3
}
func test() {
CodeBlockItem(line1)
CodeBlockItem(line2)
CodeBlockItem(line3)
}
struct MyPeerMacro: PeerMacro {
static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax]
@Peer
func abc() {}
func abc() {}
/* func _abc() {} */
struct MyAccessorMacro: AccessorMacro {
static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AccessorDeclSyntax]
@Accessor
var a: Int
var a: Int /* {...} */
struct MyMemberAttributeMacro: MemberAttributeMacro {
static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingAttributesFor member: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AttributeSyntax] {
@MemberAttribute
struct A {
var a: Int
}
struct A {
/* @xxx */
var a: Int
}
struct MyMemberMacro: MemberMacro {
static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
@Member
struct A {
}
struct A {
/* func dump() {} */
}
struct MyConformanceMacro: ConformanceMacro {
static func expansion(
of node: AttributeSyntax,
providingConformancesOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
@Conformance
struct A {}
struct A: Codable {}
@Conformance
struct A<T> {}
struct A : Codable where T: Codable {}
@Conformance
struct A {}
// struct A: Codable {}
extension A: Codable {}
@Conformance
struct A<T> {}
// struct A : Codable where T: Codable {}
extension A : Codable where T: Codable {}
Macro 可以有多個 role
在使用 macro 的地方,Swift 將展開所有適用的 role。
至少必須有一個適用的 role。
@attached(member)
@attached(memberAttribute)
@attached(accessor)
public macro composition() = #externalMacro(
module: "WWDCMacrosPlugin",
type: "CompositionMacro"
)
必須在 macro interface 定義會產生的名稱
overloaded | attached only |
prefixed(<some prefix>) | attached only, can start with $ |
suffixed(<some suffix>) | attached only |
named(<some name>) | |
arbitrary |
@attached(peer, named: overloaded)
public macro ToAsync()
@ToAsync
func call(callback: (T) -> ()) {}
func call(callback: (T) -> ()) {}
func call() async -> T {}
@attached(member, named: named(init))
public macro Init()
@Init
struct A {
}
struct A {
init() {}
}
@attached(peer, named: arbitrary)
public macro ToPublic(_ name: String)
@ToPublic("A")
private var a = 1
private var a = 1
public var A = a
@Service(resource: "quotes")
protocol QuoteService {
@GET("random")
func getRandomQuotes(@QueryParam limit: Int?)
async throws -> [Quote]
@GET("{id}")
func getQuote(@Path by id: String)
async throws -> Quote
}
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
#Point(2)
class Point2D {
let v0: Double
let v1: Double
}
@attached(accessor)
@attached(memberAttribute)
@attached(member)
macro RawJSON(name: String? = nil)
@RawJSON
struct A {
}
struct A {
let dict: [String: Any]
init(_ dict: [String: Any]) {
self.dict = dict
}
}
@RawJSON
struct A {
var a: Int
@RawJSON("B")
var b: String
}
struct A {
@RawJSON
var a: Int
@RawJSON("B")
var b: String
}
@RawJSON("B")
var b: String
var b: String {
get {
dict["B"] as! String
}
}
@PublicInit
public struct A {
let a: Int
}
public struct A {
let a: Int
public init(a: Int) {
self.a = a
}
}
macro<T> Default(_ value: T)
// return []
@PublicInit
public struct A {
@Default(1)
let a: Int
}
public struct A {
let a: Int
public init(a: Int = 1) {
self.a = a
}
}
context.makeUniqueName()
return \(literal: xxx)"
let location = contenxt.location(of: syntax)!
location.file
location.line