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