@@ -28,89 +28,117 @@ import XCTest
28
28
final class PeerMacroTests : XCTestCase {
29
29
private let indentationWidth : Trivia = . spaces( 2 )
30
30
31
- func testAddCompletionHandler( ) {
32
- struct AddCompletionHandler : PeerMacro {
33
- static func expansion(
34
- of node: AttributeSyntax ,
35
- providingPeersOf declaration: some DeclSyntaxProtocol ,
36
- in context: some MacroExpansionContext
37
- ) throws -> [ DeclSyntax ] {
38
- // Only on functions at the moment. We could handle initializers as well
39
- // with a bit of work.
40
- guard let funcDecl = declaration. as ( FunctionDeclSyntax . self) else {
41
- throw MacroExpansionErrorMessage ( " @addCompletionHandler only works on functions " )
42
- }
31
+ fileprivate struct AddCompletionHandler : PeerMacro {
32
+ static func expansion(
33
+ of node: AttributeSyntax ,
34
+ providingPeersOf declaration: some DeclSyntaxProtocol ,
35
+ in context: some MacroExpansionContext
36
+ ) throws -> [ DeclSyntax ] {
37
+ // Only on functions at the moment. We could handle initializers as well
38
+ // with a bit of work.
39
+ guard let funcDecl = declaration. as ( FunctionDeclSyntax . self) else {
40
+ throw MacroExpansionErrorMessage ( " @addCompletionHandler only works on functions " )
41
+ }
43
42
44
- // This only makes sense for async functions.
45
- if funcDecl. signature. effectSpecifiers? . asyncSpecifier == nil {
46
- throw MacroExpansionErrorMessage (
47
- " @addCompletionHandler requires an async function "
48
- )
43
+ // This only makes sense for async functions.
44
+ if funcDecl. signature. effectSpecifiers? . asyncSpecifier == nil {
45
+ let newEffects : FunctionEffectSpecifiersSyntax
46
+ if let existingEffects = funcDecl. signature. effectSpecifiers {
47
+ newEffects = existingEffects. with ( \. asyncSpecifier, . keyword( . async) )
48
+ } else {
49
+ newEffects = FunctionEffectSpecifiersSyntax ( asyncSpecifier: . keyword( . async) )
49
50
}
50
51
51
- // Form the completion handler parameter.
52
- let resultType : TypeSyntax ? = funcDecl. signature. returnClause? . type. trimmed
53
-
54
- let completionHandlerParam =
55
- FunctionParameterSyntax (
56
- firstName: . identifier( " completionHandler " ) ,
57
- colon: . colonToken( trailingTrivia: . space) ,
58
- type: TypeSyntax ( " ( \( resultType ?? " " ) ) -> Void " )
59
- )
60
-
61
- // Add the completion handler parameter to the parameter list.
62
- let parameterList = funcDecl. signature. parameterClause. parameters
63
- var newParameterList = parameterList
64
- if !parameterList. isEmpty {
65
- // We need to add a trailing comma to the preceding list.
66
- newParameterList [ newParameterList. index ( before: newParameterList. endIndex) ] . trailingComma = . commaToken( trailingTrivia: . space)
67
- }
68
- newParameterList. append ( completionHandlerParam)
52
+ let newSignature = funcDecl. signature. with ( \. effectSpecifiers, newEffects)
53
+
54
+ let diag = Diagnostic (
55
+ node: Syntax ( funcDecl. funcKeyword) ,
56
+ message: MacroExpansionErrorMessage (
57
+ " can only add a completion-handler variant to an 'async' function "
58
+ ) ,
59
+ fixIts: [
60
+ FixIt (
61
+ message: MacroExpansionFixItMessage (
62
+ " add 'async' "
63
+ ) ,
64
+ changes: [
65
+ FixIt . Change. replace (
66
+ oldNode: Syntax ( funcDecl. signature) ,
67
+ newNode: Syntax ( newSignature)
68
+ )
69
+ ]
70
+ )
71
+ ]
72
+ )
73
+
74
+ context. diagnose ( diag)
75
+ return [ ]
76
+ }
69
77
70
- let callArguments : [ String ] = parameterList. map { param in
71
- let argName = param. secondName ?? param. firstName
78
+ // Form the completion handler parameter.
79
+ let resultType : TypeSyntax ? = funcDecl. signature. returnClause? . type. trimmed
80
+
81
+ let completionHandlerParam =
82
+ FunctionParameterSyntax (
83
+ firstName: . identifier( " completionHandler " ) ,
84
+ colon: . colonToken( trailingTrivia: . space) ,
85
+ type: TypeSyntax ( " ( \( resultType ?? " " ) ) -> Void " )
86
+ )
87
+
88
+ // Add the completion handler parameter to the parameter list.
89
+ let parameterList = funcDecl. signature. parameterClause. parameters
90
+ var newParameterList = parameterList
91
+ if !parameterList. isEmpty {
92
+ // We need to add a trailing comma to the preceding list.
93
+ newParameterList [ newParameterList. index ( before: newParameterList. endIndex) ] . trailingComma = . commaToken( trailingTrivia: . space)
94
+ }
95
+ newParameterList. append ( completionHandlerParam)
72
96
73
- if param. firstName. text != " _ " {
74
- return " \( param. firstName. text) : \( argName. text) "
75
- }
97
+ let callArguments : [ String ] = parameterList. map { param in
98
+ let argName = param. secondName ?? param. firstName
76
99
77
- return " \( argName. text) "
100
+ if param. firstName. text != " _ " {
101
+ return " \( param. firstName. text) : \( argName. text) "
78
102
}
79
103
80
- let call : ExprSyntax =
81
- " \( funcDecl. name) ( \( raw: callArguments. joined ( separator: " , " ) ) ) "
82
-
83
- // FIXME: We should make CodeBlockSyntax ExpressibleByStringInterpolation,
84
- // so that the full body could go here.
85
- let newBody : ExprSyntax =
86
- """
104
+ return " \( argName. text) "
105
+ }
87
106
88
- Task {
89
- completionHandler(await \( call) )
90
- }
107
+ let call : ExprSyntax =
108
+ " \( funcDecl. name) ( \( raw: callArguments. joined ( separator: " , " ) ) ) "
91
109
92
- """
110
+ // FIXME: We should make CodeBlockSyntax ExpressibleByStringInterpolation,
111
+ // so that the full body could go here.
112
+ let newBody : ExprSyntax =
113
+ """
93
114
94
- // Drop the @addCompletionHandler attribute from the new declaration.
95
- let newAttributeList = funcDecl. attributes. filter {
96
- guard case let . attribute( attribute) = $0 else {
97
- return true
115
+ Task {
116
+ completionHandler(await \( call) )
98
117
}
99
- return attribute. attributeName. as ( IdentifierTypeSyntax . self) ? . name == " addCompletionHandler "
100
- }
101
118
102
- var newFunc = funcDecl
103
- newFunc. signature. effectSpecifiers? . asyncSpecifier = nil // drop async
104
- newFunc. signature. returnClause = nil // drop result type
105
- newFunc. signature. parameterClause. parameters = newParameterList
106
- newFunc. signature. parameterClause. trailingTrivia = [ ]
107
- newFunc. body = CodeBlockSyntax { newBody }
108
- newFunc. attributes = newAttributeList
119
+ """
109
120
110
- return [ DeclSyntax ( newFunc) ]
121
+ // Drop the @addCompletionHandler attribute from the new declaration.
122
+ let newAttributeList = funcDecl. attributes. filter {
123
+ guard case let . attribute( attribute) = $0 else {
124
+ return true
125
+ }
126
+ return attribute. attributeName. as ( IdentifierTypeSyntax . self) ? . name == " addCompletionHandler "
111
127
}
128
+
129
+ var newFunc = funcDecl
130
+ newFunc. signature. effectSpecifiers? . asyncSpecifier = nil // drop async
131
+ newFunc. signature. returnClause = nil // drop result type
132
+ newFunc. signature. parameterClause. parameters = newParameterList
133
+ newFunc. signature. parameterClause. trailingTrivia = [ ]
134
+ newFunc. body = CodeBlockSyntax { newBody }
135
+ newFunc. attributes = newAttributeList
136
+
137
+ return [ DeclSyntax ( newFunc) ]
112
138
}
139
+ }
113
140
141
+ func testAddCompletionHandler( ) {
114
142
assertMacroExpansion (
115
143
"""
116
144
@addCompletionHandler
@@ -193,4 +221,30 @@ final class PeerMacroTests: XCTestCase {
193
221
]
194
222
)
195
223
}
224
+
225
+ func testAddCompletionHandlerWhereThereIsNotAsync( ) {
226
+ assertMacroExpansion (
227
+ """
228
+ @addCompletionHandler
229
+ func f(a: Int, for b: String, _ value: Double) -> String { }
230
+ """ ,
231
+ expandedSource: """
232
+ func f(a: Int, for b: String, _ value: Double) -> String { }
233
+ """ ,
234
+ diagnostics: [
235
+ DiagnosticSpec (
236
+ message: " can only add a completion-handler variant to an 'async' function " ,
237
+ line: 2 ,
238
+ column: 1 ,
239
+ fixIts: [ FixItSpec ( message: " add 'async' " ) ]
240
+ )
241
+ ] ,
242
+ macros: [ " addCompletionHandler " : AddCompletionHandler . self] ,
243
+ fixedSource: """
244
+ @addCompletionHandler
245
+ func f(a: Int, for b: String, _ value: Double) async-> String { }
246
+ """ ,
247
+ indentationWidth: indentationWidth
248
+ )
249
+ }
196
250
}
0 commit comments