@@ -19,6 +19,15 @@ import SwiftSyntaxBuilder
19
19
/// - `distributed actor $MyDistributedActor<ActorSystem>: $MyDistributedActor, _DistributedActorStub where ...`
20
20
/// - `extension MyDistributedActor where Self: _DistributedActorStub {}`
21
21
public struct DistributedProtocolMacro : ExtensionMacro , PeerMacro {
22
+ }
23
+
24
+ // ===== -----------------------------------------------------------------------
25
+ // MARK: Default Stub implementations Extension
26
+
27
+ extension DistributedProtocolMacro {
28
+
29
+ /// Introduce the `extension MyDistributedActor` which contains default
30
+ /// implementations of the protocol's requirements.
22
31
public static func expansion(
23
32
of node: AttributeSyntax ,
24
33
attachedTo declaration: some DeclGroupSyntax ,
@@ -27,25 +36,24 @@ public struct DistributedProtocolMacro: ExtensionMacro, PeerMacro {
27
36
in context: some MacroExpansionContext
28
37
) throws -> [ ExtensionDeclSyntax ] {
29
38
guard let proto = declaration. as ( ProtocolDeclSyntax . self) else {
39
+ // we diagnose here, only once
40
+ try throwIllegalTargetDecl ( node: node, declaration)
41
+ }
42
+
43
+ guard !proto. memberBlock. members. isEmpty else {
44
+ // ok, the protocol has no requirements so we no-op it
30
45
return [ ]
31
46
}
32
47
48
+ let accessModifiers : String = proto. accessModifiersString
49
+
33
50
let requirements =
34
51
proto. memberBlock. members. map { member in
35
52
member. trimmed
36
53
}
37
54
let requirementStubs = requirements
38
- . map { req in
39
- """
40
- \( req) {
41
- if #available(SwiftStdlib 6.0, *) {
42
- Distributed._distributedStubFatalError()
43
- } else {
44
- fatalError()
45
- }
46
- }
47
- """
48
- } . joined ( separator: " \n " )
55
+ . map { stubMethod ( access: accessModifiers, $0) }
56
+ . joined ( separator: " \n " )
49
57
50
58
let extensionDecl : DeclSyntax =
51
59
"""
@@ -56,30 +64,236 @@ public struct DistributedProtocolMacro: ExtensionMacro, PeerMacro {
56
64
return [ extensionDecl. cast ( ExtensionDeclSyntax . self) ]
57
65
}
58
66
67
+ static func stubMethod( access: String , _ requirementDeclaration: MemberBlockItemListSyntax . Element ) -> String {
68
+ """
69
+ \( access) \( requirementDeclaration) {
70
+ \( stubFunctionBody ( ) )
71
+ }
72
+ """
73
+ }
74
+
75
+ static func stubFunctionBody( ) -> DeclSyntax {
76
+ """
77
+ if #available(SwiftStdlib 6.0, *) {
78
+ Distributed._distributedStubFatalError()
79
+ } else {
80
+ fatalError()
81
+ }
82
+ """
83
+ }
84
+ }
85
+
86
+ // ===== -----------------------------------------------------------------------
87
+ // MARK: Distributed Actor Stub type
88
+
89
+ extension DistributedProtocolMacro {
90
+
91
+ /// Introduce the `distributed actor` stub type.
59
92
public static func expansion(
60
93
of node: AttributeSyntax ,
61
94
providingPeersOf declaration: some DeclSyntaxProtocol ,
62
95
in context: some MacroExpansionContext
63
96
) throws -> [ DeclSyntax ] {
64
97
guard let proto = declaration. as ( ProtocolDeclSyntax . self) else {
98
+ // don't diagnose here (again),
99
+ // we'll already report an error here from the other macro role
65
100
return [ ]
66
101
}
67
102
68
- // FIXME must detect this off the protocol
69
- let serializationRequirementType =
70
- " Codable "
103
+ var isGenericStub = false
104
+ var specificActorSystemRequirement : TypeSyntax ?
71
105
72
- let stubActorDecl : DeclSyntax =
73
- """
74
- distributed actor $ \( proto. name. trimmed) <ActorSystem>: \( proto. name. trimmed) ,
75
- Distributed._DistributedActorStub
76
- where ActorSystem: DistributedActorSystem<any \( raw: serializationRequirementType) >,
77
- ActorSystem.ActorID: \( raw: serializationRequirementType)
78
- { }
79
- """
106
+ if proto. genericWhereClause == nil {
107
+ throw DiagnosticsError (
108
+ syntax: node,
109
+ message: """
110
+ Distributed protocol must declare actor system with SerializationRequirement, for example:
111
+ protocol Greeter<ActorSystem>: DistributedActor where ActorSystem: DistributedActorSystem<any Codable>
112
+ """ , id: . invalidApplication)
113
+ }
114
+
115
+ let accessModifiers = proto. accessModifiersString
116
+
117
+ for req in proto. genericWhereClause? . requirements ?? [ ] {
118
+ switch req. requirement {
119
+ case . conformanceRequirement( let conformanceReq)
120
+ where conformanceReq. leftType. isActorSystem:
121
+ specificActorSystemRequirement = conformanceReq. rightType. trimmed
122
+ isGenericStub = true
123
+
124
+ case . sameTypeRequirement( let sameTypeReq)
125
+ where sameTypeReq. leftType. isActorSystem:
126
+ specificActorSystemRequirement = sameTypeReq. rightType. trimmed
127
+ isGenericStub = false
128
+
129
+ default :
130
+ continue
131
+ }
132
+ }
133
+
134
+ if isGenericStub, let specificActorSystemRequirement {
135
+ return [
136
+ """
137
+ \( proto. modifiers) distributed actor $ \( proto. name. trimmed) <ActorSystem>: \( proto. name. trimmed) ,
138
+ Distributed._DistributedActorStub
139
+ where ActorSystem: \( specificActorSystemRequirement)
140
+ { }
141
+ """
142
+ ]
143
+ } else if let specificActorSystemRequirement {
144
+ return [
145
+ """
146
+ \( proto. modifiers) distributed actor $ \( proto. name. trimmed) : \( proto. name. trimmed) ,
147
+ Distributed._DistributedActorStub
148
+ {
149
+ \( typealiasActorSystem ( access: accessModifiers, proto, specificActorSystemRequirement) )
150
+ }
151
+ """
152
+ ]
153
+ } else {
154
+ // there may be no `where` clause specifying an actor system,
155
+ // but perhaps there is a typealias (or extension with a typealias),
156
+ // specifying a concrete actor system so we let this synthesize
157
+ // an empty `$Greeter` -- this may fail, or succeed depending on
158
+ // surrounding code using a default distributed actor system,
159
+ // or extensions providing it.
160
+ return [
161
+ """
162
+ \( proto. modifiers) distributed actor $ \( proto. name. trimmed) : \( proto. name. trimmed) ,
163
+ Distributed._DistributedActorStub
164
+ {
165
+ }
166
+ """
167
+ ]
168
+ }
169
+ }
80
170
81
- // return [extensionDecl, stubActorDecl]
82
- return [ stubActorDecl ]
171
+ private static func typealiasActorSystem ( access : String , _ proto : ProtocolDeclSyntax , _ type : TypeSyntax ) -> DeclSyntax {
172
+ " \( raw : access ) typealias ActorSystem = \( type ) "
83
173
}
174
+ }
84
175
176
+ // ===== -----------------------------------------------------------------------
177
+ // MARK: Convenience Extensions
178
+
179
+ extension ProtocolDeclSyntax {
180
+ var accessModifiersString : String {
181
+ let modifiers = modifiers. filter { modifier in
182
+ modifier. isAccessControl
183
+ }
184
+
185
+ guard !modifiers. isEmpty else {
186
+ return " "
187
+ }
188
+
189
+ let string = modifiers
190
+ . map { " \( $0. trimmed) " }
191
+ . joined ( separator: " " )
192
+ return " \( string) "
193
+ }
194
+ }
195
+
196
+ extension TypeSyntax {
197
+ fileprivate var isActorSystem : Bool {
198
+ self . trimmedDescription == " ActorSystem "
199
+ }
200
+ }
201
+
202
+ extension DeclSyntaxProtocol {
203
+ var isClass : Bool {
204
+ return self . is ( ClassDeclSyntax . self)
205
+ }
206
+
207
+ var isActor : Bool {
208
+ return self . is ( ActorDeclSyntax . self)
209
+ }
210
+
211
+ var isEnum : Bool {
212
+ return self . is ( EnumDeclSyntax . self)
213
+ }
214
+
215
+ var isStruct : Bool {
216
+ return self . is ( StructDeclSyntax . self)
217
+ }
218
+ }
219
+
220
+ extension DeclModifierSyntax {
221
+ var isAccessControl : Bool {
222
+ switch self . name. tokenKind {
223
+ case . keyword( . private) : fallthrough
224
+ case . keyword( . fileprivate) : fallthrough
225
+ case . keyword( . internal) : fallthrough
226
+ case . keyword( . package ) : fallthrough
227
+ case . keyword( . public) :
228
+ return true
229
+ default :
230
+ return false
231
+ }
232
+ }
233
+ }
234
+
235
+ // ===== -----------------------------------------------------------------------
236
+ // MARK: DistributedProtocol macro errors
237
+
238
+ extension DistributedProtocolMacro {
239
+ static func throwIllegalTargetDecl( node: AttributeSyntax , _ declaration: some DeclSyntaxProtocol ) throws -> Never {
240
+ let kind : String
241
+ if declaration. isClass {
242
+ kind = " class "
243
+ } else if declaration. isActor {
244
+ kind = " actor "
245
+ } else if declaration. isStruct {
246
+ kind = " struct "
247
+ } else if declaration. isStruct {
248
+ kind = " enum "
249
+ } else {
250
+ kind = " \( declaration. kind) "
251
+ }
252
+
253
+ throw DiagnosticsError (
254
+ syntax: node,
255
+ message: " '@DistributedProtocol' can only be applied to 'protocol', but was attached to ' \( kind) ' " , id: . invalidApplication)
256
+ }
257
+ }
258
+
259
+ struct DistributedProtocolMacroDiagnostic : DiagnosticMessage {
260
+ enum ID : String {
261
+ case invalidApplication = " invalid type "
262
+ case missingInitializer = " missing initializer "
263
+ }
264
+
265
+ var message : String
266
+ var diagnosticID : MessageID
267
+ var severity : DiagnosticSeverity
268
+
269
+ init ( message: String , diagnosticID: SwiftDiagnostics . MessageID , severity: SwiftDiagnostics . DiagnosticSeverity = . error) {
270
+ self . message = message
271
+ self . diagnosticID = diagnosticID
272
+ self . severity = severity
273
+ }
274
+
275
+ init ( message: String , domain: String , id: ID , severity: SwiftDiagnostics . DiagnosticSeverity = . error) {
276
+ self . message = message
277
+ self . diagnosticID = MessageID ( domain: domain, id: id. rawValue)
278
+ self . severity = severity
279
+ }
280
+ }
281
+
282
+ extension DiagnosticsError {
283
+ init < S: SyntaxProtocol > (
284
+ syntax: S ,
285
+ message: String ,
286
+ domain: String = " Distributed " ,
287
+ id: DistributedProtocolMacroDiagnostic . ID ,
288
+ severity: SwiftDiagnostics . DiagnosticSeverity = . error) {
289
+ self . init ( diagnostics: [
290
+ Diagnostic (
291
+ node: Syntax ( syntax) ,
292
+ message: DistributedProtocolMacroDiagnostic (
293
+ message: message,
294
+ domain: domain,
295
+ id: id,
296
+ severity: severity) )
297
+ ] )
298
+ }
85
299
}
0 commit comments