Skip to content

Commit bf52e6b

Browse files
committed
[DistributedProtocolMacro] ban attaching to wrong decl kind
1 parent f58d609 commit bf52e6b

File tree

3 files changed

+198
-32
lines changed

3 files changed

+198
-32
lines changed

lib/Macros/Sources/SwiftMacros/DistributedProtocolMacro.swift

Lines changed: 143 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ import SwiftSyntaxBuilder
1919
/// - `distributed actor $MyDistributedActor<ActorSystem>: $MyDistributedActor, _DistributedActorStub where ...`
2020
/// - `extension MyDistributedActor where Self: _DistributedActorStub {}`
2121
public struct DistributedProtocolMacro: ExtensionMacro, PeerMacro {
22+
}
23+
24+
25+
// ===== -----------------------------------------------------------------------
26+
// MARK: Default Stub implementations Extension
2227

28+
extension DistributedProtocolMacro {
2329
/// Introduce the `extension MyDistributedActor` which contains default
2430
/// implementations of the protocol's requirements.
2531
public static func expansion(
@@ -58,57 +64,167 @@ public struct DistributedProtocolMacro: ExtensionMacro, PeerMacro {
5864
"""
5965
return [extensionDecl.cast(ExtensionDeclSyntax.self)]
6066
}
67+
}
68+
69+
// ===== -----------------------------------------------------------------------
70+
// MARK: Distributed Actor Stub type
6171

72+
extension DistributedProtocolMacro {
6273
public static func expansion(
6374
of node: AttributeSyntax,
6475
providingPeersOf declaration: some DeclSyntaxProtocol,
6576
in context: some MacroExpansionContext
6677
) throws -> [DeclSyntax] {
6778
guard let proto = declaration.as(ProtocolDeclSyntax.self) else {
68-
return []
79+
try throwIllegalTargetDecl(node: node, declaration)
6980
}
7081

82+
var isGenericStub = false
83+
var specificActorSystemRequirement: TypeSyntax?
7184
// FIXME must detect this off the protocol
7285
let serializationRequirementType: String = "Codable"
73-
let specificActorSystemRequirement: TypeSyntax?
7486

7587
for req in proto.genericWhereClause?.requirements ?? [] {
7688
print("req.requirement: \(req.requirement)")
7789
switch req.requirement {
78-
case .conformanceRequirement(let conformanceReq):
90+
case .conformanceRequirement(let conformanceReq)
91+
where conformanceReq.leftType.isActorSystem:
7992
print("conf: \(conformanceReq)")
93+
specificActorSystemRequirement = conformanceReq.rightType.trimmed
94+
isGenericStub = true
8095

81-
case .sameTypeRequirement(let sameTypeReq):
96+
case .sameTypeRequirement(let sameTypeReq)
97+
where sameTypeReq.leftType.isActorSystem:
8298
print("same type: \(sameTypeReq)")
83-
if sameTypeReq.leftType.trimmedDescription == "ActorSystem" {
84-
let specificActorSystemRequirement = sameTypeReq.rightType.trimmed
85-
86-
return [
87-
"""
88-
distributed actor $\(proto.name.trimmed): \(proto.name.trimmed),
89-
Distributed._DistributedActorStub
90-
{
91-
typealias ActorSystem = \(specificActorSystemRequirement)
92-
}
93-
"""
94-
]
95-
}
96-
case .layoutRequirement(let layoutReq):
97-
print("layout: \(layoutReq)")
99+
specificActorSystemRequirement = sameTypeReq.rightType.trimmed
100+
isGenericStub = false
101+
102+
default:
103+
print("SKIP: \(req)")
104+
continue
98105
}
99106
}
100107

101108
let stubActorDecl: DeclSyntax =
102-
"""
103-
distributed actor $\(proto.name.trimmed)<ActorSystem>: \(proto.name.trimmed),
104-
Distributed._DistributedActorStub
105-
where ActorSystem: DistributedActorSystem<any \(raw: serializationRequirementType)>,
106-
ActorSystem.ActorID: \(raw: serializationRequirementType)
107-
{ }
108-
"""
109+
if (isGenericStub) {
110+
"""
111+
distributed actor $\(proto.name.trimmed)<ActorSystem>: \(proto.name.trimmed),
112+
Distributed._DistributedActorStub
113+
where ActorSystem: DistributedActorSystem<any \(raw: serializationRequirementType)>,
114+
ActorSystem.ActorID: \(raw: serializationRequirementType)
115+
{ }
116+
"""
117+
} else if let specificActorSystemRequirement {
118+
"""
119+
distributed actor $\\(proto.name.trimmed): \\(proto.name.trimmed),
120+
Distributed._DistributedActorStub
121+
{
122+
\(typealiasActorSystem(specificActorSystemRequirement))
123+
}
124+
"""
125+
} else {
126+
throw DiagnosticsError(
127+
syntax: node,
128+
message: "'@DistributedProtocolMacro' cannot be applied to ", id: .invalidApplication)
129+
}
109130

110-
// return [extensionDecl, stubActorDecl]
111131
return [stubActorDecl]
112132
}
113133

134+
private static func typealiasActorSystem(_ type: TypeSyntax) -> DeclSyntax {
135+
"typealias ActorSystem = \(type)"
136+
}
137+
}
138+
139+
// ===== -----------------------------------------------------------------------
140+
// MARK: Convenience Extensions
141+
142+
extension TypeSyntax {
143+
fileprivate var isActorSystem: Bool {
144+
self.trimmedDescription == "ActorSystem"
145+
}
146+
}
147+
148+
extension DeclSyntaxProtocol {
149+
var isClass: Bool {
150+
return self.is(ClassDeclSyntax.self)
151+
}
152+
153+
var isActor: Bool {
154+
return self.is(ActorDeclSyntax.self)
155+
}
156+
157+
var isEnum: Bool {
158+
return self.is(EnumDeclSyntax.self)
159+
}
160+
161+
var isStruct: Bool {
162+
return self.is(StructDeclSyntax.self)
163+
}
164+
}
165+
166+
// ===== -----------------------------------------------------------------------
167+
// MARK: DistributedProtocol macro errors
168+
169+
extension DistributedProtocolMacro {
170+
static func throwIllegalTargetDecl(node: AttributeSyntax, _ declaration: some DeclSyntaxProtocol) throws -> Never {
171+
let kind =
172+
if declaration.isClass {
173+
"class"
174+
} else if declaration.isActor {
175+
"actor"
176+
} else if declaration.isStruct {
177+
"struct"
178+
} else if declaration.isStruct {
179+
"enum"
180+
} else {
181+
"\(declaration.kind)"
182+
}
183+
184+
throw DiagnosticsError(
185+
syntax: node,
186+
message: "'@DistributedProtocol' can only be applied to 'protocol', but was attached to '\(kind)'", id: .invalidApplication)
187+
}
188+
}
189+
190+
struct DistributedProtocolMacroDiagnostic: DiagnosticMessage {
191+
enum ID: String {
192+
case invalidApplication = "invalid type"
193+
case missingInitializer = "missing initializer"
194+
}
195+
196+
var message: String
197+
var diagnosticID: MessageID
198+
var severity: DiagnosticSeverity
199+
200+
init(message: String, diagnosticID: SwiftDiagnostics.MessageID, severity: SwiftDiagnostics.DiagnosticSeverity = .error) {
201+
self.message = message
202+
self.diagnosticID = diagnosticID
203+
self.severity = severity
204+
}
205+
206+
init(message: String, domain: String, id: ID, severity: SwiftDiagnostics.DiagnosticSeverity = .error) {
207+
self.message = message
208+
self.diagnosticID = MessageID(domain: domain, id: id.rawValue)
209+
self.severity = severity
210+
}
211+
}
212+
213+
extension DiagnosticsError {
214+
init<S: SyntaxProtocol>(
215+
syntax: S,
216+
message: String,
217+
domain: String = "Distributed",
218+
id: DistributedProtocolMacroDiagnostic.ID,
219+
severity: SwiftDiagnostics.DiagnosticSeverity = .error) {
220+
self.init(diagnostics: [
221+
Diagnostic(
222+
node: Syntax(syntax),
223+
message: DistributedProtocolMacroDiagnostic(
224+
message: message,
225+
domain: domain,
226+
id: id,
227+
severity: severity))
228+
])
229+
}
114230
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/../Inputs/FakeDistributedActorSystems.swift
11+
// RUN: %target-swift-frontend -typecheck -verify -disable-availability-checking -plugin-path %swift-plugin-dir -parse-as-library -I %t %S/../Inputs/FakeDistributedActorSystems.swift -dump-macro-expansions %s -dump-macro-expansions 2>&1
12+
13+
import Distributed
14+
15+
@_DistributedProtocol // expected-error{{'@DistributedProtocol' can only be applied to 'protocol', but was attached to 'struct' (from macro '_DistributedProtocol')}}
16+
struct Struct {}
17+
18+
@_DistributedProtocol // expected-error{{'@DistributedProtocol' can only be applied to 'protocol', but was attached to 'class' (from macro '_DistributedProtocol')}}
19+
class Clazz {}
20+
21+
@_DistributedProtocol // expected-error{{'@DistributedProtocol' can only be applied to 'protocol', but was attached to 'actor' (from macro '_DistributedProtocol')}}
22+
actor Act {}
23+
24+
@_DistributedProtocol // expected-error{{'@DistributedProtocol' can only be applied to 'protocol', but was attached to 'actor' (from macro '_DistributedProtocol')}}
25+
distributed actor Caplin {
26+
typealias ActorSystem = FakeActorSystem
27+
}

test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_sameType.swift renamed to test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_various_requirements.swift

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212

1313
import Distributed
1414

15-
@_DistributedProtocol
16-
protocol Greeter: DistributedActor where ActorSystem == FakeActorSystem {
17-
distributed func greet(name: String) -> String
18-
}
15+
//@_DistributedProtocol
16+
//protocol Greeter: DistributedActor where ActorSystem == FakeActorSystem {
17+
// distributed func greet(name: String) -> String
18+
//}
1919

2020
// @_DistributedProtocol ->
2121

@@ -25,7 +25,30 @@ protocol Greeter: DistributedActor where ActorSystem == FakeActorSystem {
2525
// CHECK-NEXT: typealias ActorSystem = FakeActorSystem
2626
// CHECK-NEXT: }
2727

28-
// CHECK: extension Greeter where Self: Distributed._DistributedActorStub {
28+
// CHECK: extension Greeter where Self: Distributed._DistributedActorStub {
29+
// CHECK-NEXT: distributed func greet(name: String) -> String {
30+
// CHECK-NEXT: if #available (SwiftStdlib 6.0, *) {
31+
// CHECK-NEXT: Distributed._distributedStubFatalError()
32+
// CHECK-NEXT: } else {
33+
// CHECK-NEXT: fatalError()
34+
// CHECK-NEXT: }
35+
// CHECK-NEXT: }
36+
// CHECK-NEXT: }
37+
38+
@_DistributedProtocol
39+
protocol Greeter2: DistributedActor where ActorSystem: FakeRoundtripActorSystem {
40+
distributed func greet(name: String) -> String
41+
}
42+
43+
// @_DistributedProtocol ->
44+
45+
// CHECK: distributed actor $Greeter2: Greeter,
46+
//// CHECK-NEXT: Distributed._DistributedActorStub
47+
//// CHECK-NEXT: where ActorSystem: FakeActorSystem
48+
//// CHECK-NEXT: {
49+
//// CHECK-NEXT: }
50+
51+
// CHECK: extension Greeter2 where Self: Distributed._DistributedActorStub {
2952
// CHECK-NEXT: distributed func greet(name: String) -> String {
3053
// CHECK-NEXT: if #available (SwiftStdlib 6.0, *) {
3154
// CHECK-NEXT: Distributed._distributedStubFatalError()

0 commit comments

Comments
 (0)