Skip to content

Commit 827e6a5

Browse files
authored
[Distributed] Add DistributedProtocol macro (#71090)
1 parent 4fb90d3 commit 827e6a5

9 files changed

+296
-2
lines changed

lib/Macros/Sources/SwiftMacros/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
add_swift_macro_library(SwiftMacros
1414
OptionSetMacro.swift
1515
DebugDescriptionMacro.swift
16+
DistributedProtocolMacro.swift
1617
SWIFT_DEPENDENCIES
1718
SwiftDiagnostics
1819
SwiftSyntax
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import SwiftSyntax
13+
import SwiftSyntaxMacros
14+
import SwiftDiagnostics
15+
import SwiftOperators
16+
import SwiftSyntaxBuilder
17+
18+
/// Introduces:
19+
/// - `distributed actor $MyDistributedActor<ActorSystem>`
20+
public struct DistributedProtocolMacro: ExtensionMacro, PeerMacro {
21+
public static func expansion(
22+
of node: AttributeSyntax,
23+
attachedTo declaration: some DeclGroupSyntax,
24+
providingExtensionsOf type: some TypeSyntaxProtocol,
25+
conformingTo protocols: [TypeSyntax],
26+
in context: some MacroExpansionContext
27+
) throws -> [ExtensionDeclSyntax] {
28+
guard let proto = declaration.as(ProtocolDeclSyntax.self) else {
29+
return []
30+
}
31+
32+
let requirements =
33+
proto.memberBlock.members.map { member in
34+
member.trimmed
35+
}
36+
let requirementStubs = requirements
37+
.map { req in
38+
"""
39+
\(req) {
40+
if #available(SwiftStdlib 5.11, *) {
41+
Distributed._distributedStubFatalError()
42+
} else {
43+
fatalError()
44+
}
45+
}
46+
"""
47+
}.joined(separator: "\n ")
48+
49+
let extensionDecl: DeclSyntax =
50+
"""
51+
extension \(proto.name) {
52+
\(raw: requirementStubs)
53+
}
54+
"""
55+
return [extensionDecl.cast(ExtensionDeclSyntax.self)]
56+
}
57+
58+
public static func expansion(
59+
of node: AttributeSyntax,
60+
providingPeersOf declaration: some DeclSyntaxProtocol,
61+
in context: some MacroExpansionContext
62+
) throws -> [DeclSyntax] {
63+
guard let proto = declaration.as(ProtocolDeclSyntax.self) else {
64+
return []
65+
}
66+
67+
// FIXME must detect this off the protocol
68+
let serializationRequirementType =
69+
"Codable"
70+
71+
let requirements =
72+
proto.memberBlock.members.map { member in
73+
member.trimmed
74+
}
75+
let requirementStubs = requirements
76+
.map { req in
77+
"""
78+
\(req) {
79+
if #available(SwiftStdlib 5.11, *) {
80+
Distributed._distributedStubFatalError()
81+
} else {
82+
fatalError()
83+
}
84+
}
85+
"""
86+
}.joined(separator: "\n ")
87+
88+
let extensionDecl: DeclSyntax =
89+
"""
90+
extension \(proto.name) where Self: _DistributedActorStub {
91+
\(raw: requirementStubs)
92+
}
93+
"""
94+
95+
let stubActorDecl: DeclSyntax =
96+
"""
97+
distributed actor $\(proto.name)<ActorSystem>: \(proto.name), _DistributedActorStub
98+
where ActorSystem: DistributedActorSystem<any \(raw: serializationRequirementType)>,
99+
ActorSystem.ActorID: \(raw: serializationRequirementType)
100+
{ }
101+
"""
102+
103+
// return [extensionDecl, stubActorDecl]
104+
return [stubActorDecl]
105+
}
106+
107+
}

stdlib/public/Distributed/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ add_swift_target_library(swiftDistributed ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS
2626
DistributedActorSystem.swift
2727
DistributedAssertions.swift
2828
DistributedDefaultExecutor.swift
29+
DistributedMacros.swift
2930
DistributedMetadata.swift
3031
LocalTestingDistributedActorSystem.swift
3132

@@ -45,10 +46,13 @@ add_swift_target_library(swiftDistributed ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS
4546
C_COMPILE_FLAGS
4647
-DswiftDistributed_EXPORTS
4748
-I${SWIFT_SOURCE_DIR}/stdlib/include
49+
4850
SWIFT_COMPILE_FLAGS
4951
${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS}
5052
-parse-stdlib
51-
LINK_FLAGS "${SWIFT_RUNTIME_SWIFT_LINK_FLAGS}"
53+
54+
LINK_FLAGS
55+
"${SWIFT_RUNTIME_SWIFT_LINK_FLAGS}"
5256

5357
SWIFT_MODULE_DEPENDS _Concurrency
5458
INSTALL_IN_COMPONENT stdlib

stdlib/public/Distributed/DistributedActor.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,13 @@ public func __isLocalActor(_ actor: AnyObject) -> Bool {
439439

440440
@_silgen_name("swift_distributedActor_remote_initialize")
441441
func _distributedActorRemoteInitialize(_ actorType: Builtin.RawPointer) -> Any
442+
443+
// ==== Distributed Actor Stubs ------------------------------------------------
444+
445+
@available(SwiftStdlib 5.11, *)
446+
public protocol _DistributedActorStub where Self: DistributedActor {}
447+
448+
@available(SwiftStdlib 5.11, *)
449+
public func _distributedStubFatalError(function: String = #function) -> Never {
450+
fatalError("Unexpected invocation of distributed method '\(function)' stub!")
451+
}

stdlib/public/Distributed/DistributedActorSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ public protocol DistributedActorSystem<SerializationRequirement>: Sendable {
256256
/// If this type is ``Codable``, then any `distributed actor` using this `ActorID` as its ``DistributedActor/ID``
257257
/// will gain a synthesized ``Codable`` conformance which is implemented by encoding the `ID`.
258258
/// The decoding counter part of the ``Codable`` conformance is implemented by decoding the `ID` and passing it to
259-
// the ``DistributedActor/resolve(id:using:)`` method.
259+
/// the ``DistributedActor/resolve(id:using:)`` method.
260260
associatedtype ActorID: Sendable & Hashable
261261

262262
/// Type of ``DistributedTargetInvocationEncoder`` that should be used when the Swift runtime needs to encode
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020-2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
// Macros supporting distributed actor features.
13+
//===----------------------------------------------------------------------===//
14+
15+
import Swift
16+
import _Concurrency
17+
18+
#if $Macros
19+
20+
@attached(peer, names: prefixed(`$`)) // provides $Greeter concrete stub type
21+
@attached(extension, names: arbitrary) // provides extension for Greeter & _DistributedActorStub
22+
public macro _DistributedProtocol() =
23+
#externalMacro(module: "SwiftMacros", type: "DistributedProtocolMacro")
24+
25+
#endif
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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 -disable-availability-checking -plugin-path %swift-plugin-dir -I %t -dump-macro-expansions %s -dump-macro-expansions 2>&1 | %FileCheck %s
11+
12+
// FIXME: inheritance tests limited because cannot refer to any generated macro from the same module...
13+
// XFAIL: *
14+
15+
import Distributed
16+
17+
typealias System = LocalTestingDistributedActorSystem
18+
19+
@_DistributedProtocol
20+
protocol EmptyBase {}
21+
22+
// TODO: allow this?
23+
//@_DistributedProtocol
24+
//extension EmptyBase {}
25+
26+
// @_DistributedProtocol ->
27+
//
28+
// CHECK: @freestanding(declaration)
29+
// CHECK: macro _distributed_stubs_EmptyBase() =
30+
// CHECK: #distributedStubs(
31+
// CHECK: module: "main", protocolName: "EmptyBase",
32+
// CHECK: stubProtocols: []
33+
// CHECK: )
34+
//
35+
// CHECK: // distributed actor $EmptyBase <ActorSystem>: EmptyBase where SerializationRequirement == any Codable {
36+
// CHECK: distributed actor $EmptyBase : EmptyBase {
37+
// CHECK: typealias ActorSystem = LocalTestingDistributedActorSystem // FIXME: remove this
38+
//
39+
// CHECK: #distributedStubs(
40+
// CHECK: module: "main", protocolName: "EmptyBase",
41+
// CHECK: stubProtocols: []
42+
// CHECK: )
43+
// CHECK: }
44+
45+
@_DistributedProtocol
46+
protocol G3: DistributedActor, EmptyBase where SerializationRequirement == any Codable {
47+
distributed func get() -> String
48+
distributed func greet(name: String) -> String
49+
}
50+
51+
// @_DistributedProtocol ->
52+
//
53+
// Since we have also the EmptyBase we don't know what names it will introduce,
54+
// so this stubs macro must be "names: arbitrary":
55+
// CHECK: @freestanding(declaration, names: arbitrary)
56+
// CHECK: macro _distributed_stubs_G3() =
57+
// CHECK: #distributedStubs(
58+
// CHECK: module: "main", protocolName: "G3",
59+
// CHECK: stubProtocols: ["EmptyBase"],
60+
// CHECK: "distributed func get() -> String",
61+
// CHECK: "distributed func greet(name: String) -> String"
62+
// CHECK: )
63+
//
64+
// TODO: distributed actor $G3<ActorSystem>: Greeter where SerializationRequirement == any Codable {
65+
// CHECK: distributed actor $G3: G3, EmptyBase {
66+
// TODO: Preferably, we could refer to our own macro like this: #_distributed_stubs_G3
67+
// WORKAROUND:
68+
// CHECK: #distributedStubs(
69+
// CHECK: module: "main", protocolName: "G3",
70+
// CHECK: stubProtocols: ["EmptyBase"],
71+
// CHECK: "distributed func get() -> String",
72+
// CHECK: "distributed func greet(name: String) -> String"
73+
// CHECK: )
74+
// CHECK:
75+
// FIXME: the below cannot find the macro because it's form the same module
76+
// CHECK: // stub inherited members
77+
// CHECK: #_distributed_stubs_EmptyBase
78+
// CHECK: }
79+
80+
// ==== ------------------------------------------------------------------------
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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: not %target-swift-frontend -typecheck -verify -disable-availability-checking -plugin-path %swift-plugin-dir -I %t -dump-macro-expansions %s 2>&1 | %FileCheck %s
11+
12+
// FIXME: rdar://123012943 witnesses of get() and greet(name:) are not found
13+
// After that, this test SHOULD fail, but only because of the notStubbed
14+
// XFAIL: *
15+
16+
import Distributed
17+
18+
protocol EmptyBase {
19+
func notStubbed()
20+
}
21+
22+
@_DistributedProtocol
23+
protocol Fail: DistributedActor, EmptyBase where SerializationRequirement == any Codable {
24+
distributed func get() -> String
25+
distributed func greet(name: String) -> String
26+
}
27+
28+
// CHECK: FAIL
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 -disable-availability-checking -plugin-path %swift-plugin-dir -I %t -dump-macro-expansions %s -dump-macro-expansions 2>&1 | %FileCheck %s
11+
12+
import Distributed
13+
14+
// FIXME: the errors below are bugs: the added methods should be considered witnesses rdar://123012943
15+
// expected-note@+1{{in expansion of macro '_DistributedProtocol' on protocol 'Greeter' here}}
16+
@_DistributedProtocol
17+
protocol Greeter: DistributedActor where ActorSystem: DistributedActorSystem<any Codable> {
18+
// FIXME: this is a bug
19+
// expected-note@+1{{protocol requires function 'greet(name:)' with type '(String) -> String'}}
20+
distributed func greet(name: String) -> String
21+
}
22+
23+
// @_DistributedProtocol ->
24+
25+
// CHECK: distributed actor $Greeter<ActorSystem>: Greeter, _DistributedActorStub
26+
// CHECK: where ActorSystem: DistributedActorSystem<any Codable>,
27+
// CHECK: ActorSystem.ActorID: Codable
28+
// CHECK: {
29+
// CHECK: }
30+
31+
// CHECK: extension Greeter {
32+
// CHECK: distributed func greet(name: String) -> String {
33+
// CHECK: if #available (SwiftStdlib 5.11, *) {
34+
// CHECK: Distributed._distributedStubFatalError()
35+
// CHECK: } else {
36+
// CHECK: fatalError()
37+
// CHECK: }
38+
// CHECK: }
39+
// CHECK: }

0 commit comments

Comments
 (0)