Skip to content

Commit 138f145

Browse files
committed
[Distributed] @resolvable now handles primary associated types in protocols
Previously we would not propagate those into the generated distributed actor, making a lot of generic distributed actor protocols impossible to express. We indeed cannot handle protocols WITHOUT primary associated types, but we certainly can handle them with! This resolves rdar://139332556
1 parent 31cc6b8 commit 138f145

File tree

4 files changed

+181
-36
lines changed

4 files changed

+181
-36
lines changed

include/swift/AST/KnownProtocols.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ PROTOCOL(Differentiable)
119119

120120
// Distributed Actors
121121
PROTOCOL(DistributedActor)
122+
PROTOCOL_(DistributedActorStub)
122123
PROTOCOL(DistributedActorSystem)
123124
PROTOCOL(DistributedTargetInvocationEncoder)
124125
PROTOCOL(DistributedTargetInvocationDecoder)

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6956,6 +6956,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
69566956
case KnownProtocolKind::Identifiable:
69576957
case KnownProtocolKind::Actor:
69586958
case KnownProtocolKind::DistributedActor:
6959+
case KnownProtocolKind::DistributedActorStub:
69596960
case KnownProtocolKind::DistributedActorSystem:
69606961
case KnownProtocolKind::DistributedTargetInvocationEncoder:
69616962
case KnownProtocolKind::DistributedTargetInvocationDecoder:

lib/Macros/Sources/SwiftMacros/DistributedResolvableMacro.swift

Lines changed: 90 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ extension DistributedResolvableMacro {
130130
return []
131131
}
132132

133-
var isGenericStub = false
133+
var isGenericOverActorSystem = false
134134
var specificActorSystemRequirement: TypeSyntax?
135135

136136
let accessModifiers = proto.accessControlModifiers
@@ -140,15 +140,15 @@ extension DistributedResolvableMacro {
140140
case .conformanceRequirement(let conformanceReq)
141141
where conformanceReq.leftType.isActorSystem:
142142
specificActorSystemRequirement = conformanceReq.rightType.trimmed
143-
isGenericStub = true
143+
isGenericOverActorSystem = true
144144

145145
case .sameTypeRequirement(let sameTypeReq):
146146
switch sameTypeReq.leftType {
147147
case .type(let type) where type.isActorSystem:
148148
switch sameTypeReq.rightType.trimmed {
149149
case .type(let rightType):
150150
specificActorSystemRequirement = rightType
151-
isGenericStub = false
151+
isGenericOverActorSystem = false
152152

153153
case .expr:
154154
throw DiagnosticsError(
@@ -167,41 +167,78 @@ extension DistributedResolvableMacro {
167167
}
168168
}
169169

170-
if isGenericStub, let specificActorSystemRequirement {
171-
return [
172-
"""
173-
\(proto.modifiers) distributed actor $\(proto.name.trimmed)<ActorSystem>: \(proto.name.trimmed),
174-
Distributed._DistributedActorStub
175-
where ActorSystem: \(specificActorSystemRequirement)
176-
{ }
177-
"""
178-
]
179-
} else if let specificActorSystemRequirement {
180-
return [
181-
"""
182-
\(proto.modifiers) distributed actor $\(proto.name.trimmed): \(proto.name.trimmed),
183-
Distributed._DistributedActorStub
184-
{
185-
\(typealiasActorSystem(access: accessModifiers, proto, specificActorSystemRequirement))
186-
}
187-
"""
188-
]
189-
} else {
190-
// there may be no `where` clause specifying an actor system,
191-
// but perhaps there is a typealias (or extension with a typealias),
192-
// specifying a concrete actor system so we let this synthesize
193-
// an empty `$Greeter` -- this may fail, or succeed depending on
194-
// surrounding code using a default distributed actor system,
195-
// or extensions providing it.
196-
return [
197-
"""
198-
\(proto.modifiers) distributed actor $\(proto.name.trimmed): \(proto.name.trimmed),
199-
Distributed._DistributedActorStub
200-
{
170+
var primaryAssociatedTypes: [PrimaryAssociatedTypeSyntax] = []
171+
if let primaryTypes = proto.primaryAssociatedTypeClause?.primaryAssociatedTypes {
172+
primaryAssociatedTypes.append(contentsOf: primaryTypes)
173+
}
174+
175+
// The $Stub is always generic over the actor system: $Stub<ActorSystem>
176+
var primaryTypeParams: [String] = primaryAssociatedTypes.map {
177+
$0.as(PrimaryAssociatedTypeSyntax.self)!.name.trimmed.text
178+
}
179+
180+
// Don't duplicate the ActorSystem type parameter if it already was declared
181+
// on the protocol as a primary associated type;
182+
// otherwise, add it as first primary associated type.
183+
let actorSystemTypeParam: [String] =
184+
if primaryTypeParams.contains("ActorSystem") {
185+
[]
186+
} else if isGenericOverActorSystem {
187+
["ActorSystem"]
188+
} else {
189+
[]
190+
}
191+
192+
// Prepend the actor system type parameter, as we want it to be the first one
193+
primaryTypeParams = actorSystemTypeParam + primaryTypeParams
194+
let typeParamsClause =
195+
primaryTypeParams.isEmpty ? "" : "<" + primaryTypeParams.joined(separator: ", ") + ">"
196+
197+
var whereClause: String = ""
198+
do {
199+
let associatedTypeDecls = proto.associatedTypeDecls
200+
var typeParamConstraints: [String] = []
201+
for typeParamName in primaryTypeParams {
202+
if let decl = associatedTypeDecls[typeParamName] {
203+
if let inheritanceClause = decl.inheritanceClause {
204+
typeParamConstraints.append("\(typeParamName)\(inheritanceClause)")
205+
}
201206
}
202-
"""
203-
]
207+
}
208+
209+
if isGenericOverActorSystem, let specificActorSystemRequirement {
210+
typeParamConstraints = ["ActorSystem: \(specificActorSystemRequirement)"] + typeParamConstraints
211+
}
212+
213+
if !typeParamConstraints.isEmpty {
214+
whereClause += "\n where " + typeParamConstraints.joined(separator: ",\n ")
215+
}
204216
}
217+
218+
let stubActorBody: String =
219+
if isGenericOverActorSystem {
220+
// there may be no `where` clause specifying an actor system,
221+
// but perhaps there is a typealias (or extension with a typealias),
222+
// specifying a concrete actor system so we let this synthesize
223+
// an empty `$Greeter` -- this may fail, or succeed depending on
224+
// surrounding code using a default distributed actor system,
225+
// or extensions providing it.
226+
""
227+
} else if let specificActorSystemRequirement {
228+
"\(typealiasActorSystem(access: accessModifiers, proto, specificActorSystemRequirement))"
229+
} else {
230+
""
231+
}
232+
233+
return [
234+
"""
235+
\(proto.modifiers) distributed actor $\(proto.name.trimmed)\(raw: typeParamsClause): \(proto.name.trimmed),
236+
Distributed._DistributedActorStub \(raw: whereClause)
237+
{
238+
\(raw: stubActorBody)
239+
}
240+
"""
241+
]
205242
}
206243

207244
private static func typealiasActorSystem(access: DeclModifierListSyntax,
@@ -253,6 +290,23 @@ extension DeclModifierSyntax {
253290
}
254291
}
255292

293+
extension ProtocolDeclSyntax {
294+
var associatedTypeDecls: [String: AssociatedTypeDeclSyntax] {
295+
let visitor = AssociatedTypeDeclVisitor(viewMode: .all)
296+
visitor.walk(self)
297+
return visitor.associatedTypeDecls
298+
}
299+
300+
final class AssociatedTypeDeclVisitor: SyntaxVisitor {
301+
var associatedTypeDecls: [String: AssociatedTypeDeclSyntax] = [:]
302+
303+
override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind {
304+
associatedTypeDecls[node.name.text] = node
305+
return .skipChildren
306+
}
307+
}
308+
}
309+
256310
// ===== -----------------------------------------------------------------------
257311
// MARK: @Distributed.Resolvable macro errors
258312

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// REQUIRES: swift_swift_parser, asserts
2+
//
3+
// UNSUPPORTED: back_deploy_concurrency
4+
// REQUIRES: concurrency
5+
// REQUIRES: distributed
6+
//
7+
// RUN: %empty-directory(%t)
8+
// RUN: %empty-directory(%t-scratch)
9+
10+
// RUN: %target-swift-frontend -typecheck -verify -target %target-swift-6.0-abi-triple -plugin-path %swift-plugin-dir -I %t -dump-macro-expansions %s 2>&1 | %FileCheck %s
11+
12+
import Distributed
13+
14+
typealias System = LocalTestingDistributedActorSystem
15+
16+
@Resolvable
17+
protocol Base<Fruit>: DistributedActor where ActorSystem: DistributedActorSystem<any Codable> {
18+
associatedtype Fruit: Codable
19+
distributed func get() -> Fruit
20+
}
21+
// CHECK: distributed actor $Base<ActorSystem, Fruit>: Base
22+
// CHECK-NEXT: Distributed._DistributedActorStub
23+
// CHECK-NEXT: where ActorSystem: DistributedActorSystem<any Codable>,
24+
// CHECK-NEXT: Fruit: Codable
25+
// CHECK-NEXT: {
26+
// CHECK: }
27+
28+
@Resolvable
29+
protocol Base2<Fruit, Animal>: DistributedActor where ActorSystem: DistributedActorSystem<any Codable> {
30+
associatedtype Fruit: Codable
31+
associatedtype Animal: Codable
32+
distributed func get(animal: Animal) -> Fruit
33+
}
34+
// CHECK: distributed actor $Base2<ActorSystem, Fruit, Animal>: Base
35+
// CHECK-NEXT: Distributed._DistributedActorStub
36+
// CHECK-NEXT: where ActorSystem: DistributedActorSystem<any Codable>,
37+
// CHECK-NEXT: Fruit: Codable,
38+
// CHECK-NEXT: Animal: Codable
39+
// CHECK-NEXT: {
40+
// CHECK: }
41+
42+
@Resolvable
43+
protocol Base3<Fruit, Animal>: DistributedActor where ActorSystem: DistributedActorSystem<any Codable> {
44+
associatedtype Fruit: Codable
45+
associatedtype Animal: Codable & Hashable
46+
distributed func get(animal: Animal) -> Fruit
47+
}
48+
// CHECK: distributed actor $Base3<ActorSystem, Fruit, Animal>: Base
49+
// CHECK-NEXT: Distributed._DistributedActorStub
50+
// CHECK-NEXT: where ActorSystem: DistributedActorSystem<any Codable>,
51+
// CHECK-NEXT: Fruit: Codable,
52+
// CHECK-NEXT: Animal: Codable & Hashable
53+
// CHECK-NEXT: {
54+
// CHECK: }
55+
56+
/// This type is not generic over the actor system because of the == constraint:
57+
@Resolvable
58+
protocol Base4<Fruit, Animal>: DistributedActor where ActorSystem == LocalTestingDistributedActorSystem {
59+
associatedtype Fruit: Codable
60+
associatedtype Animal: Codable & Hashable
61+
62+
distributed func get(animal: Animal) -> Fruit
63+
}
64+
// CHECK: distributed actor $Base4<Fruit, Animal>: Base
65+
// CHECK-NEXT: Distributed._DistributedActorStub
66+
// CHECK-NEXT: Fruit: Codable,
67+
// CHECK-NEXT: Animal: Codable & Hashable
68+
// CHECK-NEXT: {
69+
// CHECK-NEXT: typealias ActorSystem = LocalTestingDistributedActorSystem
70+
// CHECK-NEXT: }
71+
72+
@Resolvable
73+
public protocol DistributedWorker<WorkItem, WorkResult>: DistributedActor where ActorSystem == LocalTestingDistributedActorSystem {
74+
associatedtype WorkItem: Sendable & Codable
75+
associatedtype WorkResult: Sendable & Codable
76+
77+
distributed func dist_sync(work: WorkItem) -> WorkResult
78+
distributed func dist_async(work: WorkItem) async -> WorkResult
79+
distributed func dist_syncThrows(work: WorkItem) throws -> WorkResult
80+
distributed func dist_asyncThrows(work: WorkItem) async throws -> WorkResult
81+
}
82+
83+
// CHECK: public distributed actor $DistributedWorker<WorkItem, WorkResult>: DistributedWorker
84+
// CHECK-NEXT: Distributed._DistributedActorStub
85+
// CHECK-NEXT: WorkItem: Sendable & Codable,
86+
// CHECK-NEXT: WorkResult: Sendable & Codable
87+
// CHECK-NEXT: {
88+
// CHECK: public typealias ActorSystem = LocalTestingDistributedActorSystem
89+
// CHECK-NEXT: }

0 commit comments

Comments
 (0)