@@ -19,6 +19,15 @@ import SwiftSyntaxBuilder
1919/// - `distributed actor $MyDistributedActor<ActorSystem>: $MyDistributedActor, _DistributedActorStub where ...`
2020/// - `extension MyDistributedActor where Self: _DistributedActorStub {}`
2121public 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.
2231 public static func expansion(
2332 of node: AttributeSyntax ,
2433 attachedTo declaration: some DeclGroupSyntax ,
@@ -27,25 +36,24 @@ public struct DistributedProtocolMacro: ExtensionMacro, PeerMacro {
2736 in context: some MacroExpansionContext
2837 ) throws -> [ ExtensionDeclSyntax ] {
2938 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
3045 return [ ]
3146 }
3247
48+ let accessModifiers : String = proto. accessModifiersString
49+
3350 let requirements =
3451 proto. memberBlock. members. map { member in
3552 member. trimmed
3653 }
3754 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 " )
4957
5058 let extensionDecl : DeclSyntax =
5159 """
@@ -56,30 +64,236 @@ public struct DistributedProtocolMacro: ExtensionMacro, PeerMacro {
5664 return [ extensionDecl. cast ( ExtensionDeclSyntax . self) ]
5765 }
5866
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.
5992 public static func expansion(
6093 of node: AttributeSyntax ,
6194 providingPeersOf declaration: some DeclSyntaxProtocol ,
6295 in context: some MacroExpansionContext
6396 ) throws -> [ DeclSyntax ] {
6497 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
65100 return [ ]
66101 }
67102
68- // FIXME must detect this off the protocol
69- let serializationRequirementType =
70- " Codable "
103+ var isGenericStub = false
104+ var specificActorSystemRequirement : TypeSyntax ?
71105
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+ }
80170
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 ) "
83173 }
174+ }
84175
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+ }
85299}
0 commit comments