Skip to content

Commit 3c160b9

Browse files
committed
Move macro examples code to repo as-is
- Code was sourced from Doug's repo and moved without any alterations except for formatting changes. - Original target names have been updated to more descriptive ones. - References in macros to target names have been updated accordingly.
1 parent 1060677 commit 3c160b9

25 files changed

+2338
-1
lines changed

Examples/Package.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// swift-tools-version: 5.7
1+
// swift-tools-version: 5.9
22

33
import PackageDescription
4+
import CompilerPluginSupport
45

56
let package = Package(
67
name: "Examples",
@@ -38,6 +39,37 @@ let package = Package(
3839
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
3940
]
4041
),
42+
.macro(
43+
name: "MacroExamplesImplementation",
44+
dependencies: [
45+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
46+
.product(name: "SwiftSyntax", package: "swift-syntax"),
47+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
48+
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
49+
],
50+
path: "Sources/MacroExamples/Implementation"
51+
),
52+
.target(
53+
name: "MacroExamplesInterface",
54+
dependencies: [
55+
"MacroExamplesImplementation"
56+
],
57+
path: "Sources/MacroExamples/Interface"
58+
),
59+
.executableTarget(
60+
name: "MacroExamplesPlayground",
61+
dependencies: [
62+
"MacroExamplesInterface"
63+
],
64+
path: "Sources/MacroExamples/Playground"
65+
),
66+
.testTarget(
67+
name: "MacroExamplesImplementationTests",
68+
dependencies: [
69+
"MacroExamplesImplementation"
70+
],
71+
path: "Tests/MacroExamples/Implementation"
72+
),
4173
]
4274
)
4375

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
import SwiftSyntaxMacros
15+
16+
extension SyntaxCollection {
17+
mutating func removeLast() {
18+
self.remove(at: self.index(before: self.endIndex))
19+
}
20+
}
21+
22+
public struct AddAsyncMacro: PeerMacro {
23+
public static func expansion<
24+
Context: MacroExpansionContext,
25+
Declaration: DeclSyntaxProtocol
26+
>(
27+
of node: AttributeSyntax,
28+
providingPeersOf declaration: Declaration,
29+
in context: Context
30+
) throws -> [DeclSyntax] {
31+
32+
// Only on functions at the moment.
33+
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
34+
throw CustomError.message("@addAsync only works on functions")
35+
}
36+
37+
// This only makes sense for non async functions.
38+
if funcDecl.signature.effectSpecifiers?.asyncSpecifier != nil {
39+
throw CustomError.message(
40+
"@addAsync requires an non async function"
41+
)
42+
}
43+
44+
// This only makes sense void functions
45+
if funcDecl.signature.returnClause?.type.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description != "Void" {
46+
throw CustomError.message(
47+
"@addAsync requires an function that returns void"
48+
)
49+
}
50+
51+
// Requires a completion handler block as last parameter
52+
guard let completionHandlerParameterAttribute = funcDecl.signature.parameterClause.parameters.last?.type.as(AttributedTypeSyntax.self),
53+
let completionHandlerParameter = completionHandlerParameterAttribute.baseType.as(FunctionTypeSyntax.self)
54+
else {
55+
throw CustomError.message(
56+
"@addAsync requires an function that has a completion handler as last parameter"
57+
)
58+
}
59+
60+
// Completion handler needs to return Void
61+
if completionHandlerParameter.returnClause.type.with(\.leadingTrivia, []).with(\.trailingTrivia, []).description != "Void" {
62+
throw CustomError.message(
63+
"@addAsync requires an function that has a completion handler that returns Void"
64+
)
65+
}
66+
67+
let returnType = completionHandlerParameter.parameters.first?.type
68+
69+
let isResultReturn = returnType?.children(viewMode: .all).first?.description == "Result"
70+
let successReturnType = isResultReturn ? returnType!.as(IdentifierTypeSyntax.self)!.genericArgumentClause?.arguments.first!.argument : returnType
71+
72+
// Remove completionHandler and comma from the previous parameter
73+
var newParameterList = funcDecl.signature.parameterClause.parameters
74+
newParameterList.removeLast()
75+
let newParameterListLastParameter = newParameterList.last!
76+
newParameterList.removeLast()
77+
newParameterList.append(newParameterListLastParameter.with(\.trailingTrivia, []).with(\.trailingComma, nil))
78+
79+
// Drop the @addAsync attribute from the new declaration.
80+
let newAttributeList = AttributeListSyntax(
81+
funcDecl.attributes?.filter {
82+
guard case let .attribute(attribute) = $0,
83+
let attributeType = attribute.attributeName.as(IdentifierTypeSyntax.self),
84+
let nodeType = node.attributeName.as(IdentifierTypeSyntax.self)
85+
else {
86+
return true
87+
}
88+
89+
return attributeType.name.text != nodeType.name.text
90+
} ?? []
91+
)
92+
93+
let callArguments: [String] = newParameterList.map { param in
94+
let argName = param.secondName ?? param.firstName
95+
96+
let paramName = param.firstName
97+
if paramName.text != "_" {
98+
return "\(paramName.text): \(argName.text)"
99+
}
100+
101+
return "\(argName.text)"
102+
}
103+
104+
let switchBody: ExprSyntax =
105+
"""
106+
switch returnValue {
107+
case .success(let value):
108+
continuation.resume(returning: value)
109+
case .failure(let error):
110+
continuation.resume(throwing: error)
111+
}
112+
"""
113+
114+
let newBody: ExprSyntax =
115+
"""
116+
117+
\(isResultReturn ? "try await withCheckedThrowingContinuation { continuation in" : "await withCheckedContinuation { continuation in")
118+
\(funcDecl.name)(\(raw: callArguments.joined(separator: ", "))) { \(returnType != nil ? "returnValue in" : "")
119+
120+
\(isResultReturn ? switchBody : "continuation.resume(returning: \(returnType != nil ? "returnValue" : "()"))")
121+
122+
}
123+
}
124+
125+
"""
126+
127+
let newFunc =
128+
funcDecl
129+
.with(
130+
\.signature,
131+
funcDecl.signature
132+
.with(
133+
\.effectSpecifiers,
134+
FunctionEffectSpecifiersSyntax(leadingTrivia: .space, asyncSpecifier: "async", throwsSpecifier: isResultReturn ? " throws" : nil) // add async
135+
)
136+
.with(
137+
\.returnClause,
138+
successReturnType != nil ? ReturnClauseSyntax(leadingTrivia: .space, type: successReturnType!.with(\.leadingTrivia, .space)) : nil
139+
) // add result type
140+
.with(
141+
\.parameterClause,
142+
funcDecl.signature.parameterClause.with(\.parameters, newParameterList) // drop completion handler
143+
.with(\.trailingTrivia, [])
144+
)
145+
)
146+
.with(
147+
\.body,
148+
CodeBlockSyntax(
149+
leftBrace: .leftBraceToken(leadingTrivia: .space),
150+
statements: CodeBlockItemListSyntax(
151+
[CodeBlockItemSyntax(item: .expr(newBody))]
152+
),
153+
rightBrace: .rightBraceToken(leadingTrivia: .newline)
154+
)
155+
)
156+
.with(\.attributes, newAttributeList)
157+
.with(\.leadingTrivia, .newlines(2))
158+
159+
return [DeclSyntax(newFunc)]
160+
}
161+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntaxMacros
14+
import SwiftSyntax
15+
import SwiftOperators
16+
import SwiftDiagnostics
17+
18+
/// Implementation of the `addBlocker` macro, which demonstrates how to
19+
/// produce detailed diagnostics from a macro implementation for an utterly
20+
/// silly task: warning about every "add" (binary +) in the argument, with a
21+
/// Fix-It that changes it to a "-".
22+
public struct AddBlocker: ExpressionMacro {
23+
class AddVisitor: SyntaxRewriter {
24+
var diagnostics: [Diagnostic] = []
25+
26+
override func visit(
27+
_ node: InfixOperatorExprSyntax
28+
) -> ExprSyntax {
29+
// Identify any infix operator + in the tree.
30+
if let binOp = node.operator.as(BinaryOperatorExprSyntax.self) {
31+
if binOp.operator.text == "+" {
32+
// Form the warning
33+
let messageID = MessageID(domain: "silly", id: "addblock")
34+
diagnostics.append(
35+
Diagnostic(
36+
// Where the warning should go (on the "+").
37+
node: Syntax(node.operator),
38+
// The warning message and severity.
39+
message: SimpleDiagnosticMessage(
40+
message: "blocked an add; did you mean to subtract?",
41+
diagnosticID: messageID,
42+
severity: .warning
43+
),
44+
// Highlight the left and right sides of the `+`.
45+
highlights: [
46+
Syntax(node.leftOperand),
47+
Syntax(node.rightOperand),
48+
],
49+
fixIts: [
50+
// Fix-It to replace the '+' with a '-'.
51+
FixIt(
52+
message: SimpleDiagnosticMessage(
53+
message: "use '-'",
54+
diagnosticID: messageID,
55+
severity: .error
56+
),
57+
changes: [
58+
FixIt.Change.replace(
59+
oldNode: Syntax(binOp.operator),
60+
newNode: Syntax(
61+
TokenSyntax(
62+
.binaryOperator("-"),
63+
leadingTrivia: binOp.operator.leadingTrivia,
64+
trailingTrivia: binOp.operator.trailingTrivia,
65+
presence: .present
66+
)
67+
)
68+
)
69+
]
70+
)
71+
]
72+
)
73+
)
74+
75+
return ExprSyntax(
76+
node.with(
77+
\.operator,
78+
ExprSyntax(
79+
binOp.with(
80+
\.operator,
81+
binOp.operator.with(\.tokenKind, .binaryOperator("-"))
82+
)
83+
)
84+
)
85+
)
86+
}
87+
}
88+
89+
return ExprSyntax(node)
90+
}
91+
}
92+
93+
public static func expansion(
94+
of node: some FreestandingMacroExpansionSyntax,
95+
in context: some MacroExpansionContext
96+
) throws -> ExprSyntax {
97+
let visitor = AddVisitor()
98+
let result = visitor.rewrite(Syntax(node))
99+
100+
for diag in visitor.diagnostics {
101+
context.diagnose(diag)
102+
}
103+
104+
return result.asProtocol(FreestandingMacroExpansionSyntax.self)!.argumentList.first!.expression
105+
}
106+
}

0 commit comments

Comments
 (0)