Skip to content

Commit 7792a31

Browse files
committed
[Distributed] Implement missing case in permitting witnesses
1 parent 9857464 commit 7792a31

File tree

3 files changed

+92
-9
lines changed

3 files changed

+92
-9
lines changed

include/swift/AST/Decl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3644,7 +3644,7 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
36443644
bool isActor() const;
36453645

36463646
/// Whether this nominal type qualifies as a distributed actor, meaning that
3647-
/// it is either a distributed actor.
3647+
/// it is either a distributed actor or a DistributedActor constrained protocol.
36483648
bool isDistributedActor() const;
36493649

36503650
/// Whether this nominal type qualifies as any actor (plain or distributed).

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3029,14 +3029,47 @@ bool ConformanceChecker::checkActorIsolation(
30293029
bool isDistributed = refResult.isolation.isDistributedActor() &&
30303030
!witness->getAttrs().hasAttribute<NonisolatedAttr>();
30313031
if (isDistributed) {
3032-
// If we're coming from a non-distributed requirement, then the requirement
3033-
// must be 'throws' to accommodate failure.
3034-
if (!isDistributedDecl(requirement) && !isThrowsDecl(requirement))
3035-
missingOptions |= MissingFlags::RequirementThrows;
3036-
3037-
if (!isDistributedDecl(witness) &&
3038-
(isDistributedDecl(requirement) || !missingOptions))
3039-
missingOptions |= MissingFlags::WitnessDistributed;
3032+
// Check if the protocol where the requirement originates from
3033+
// is a distributed actor constrained one.
3034+
auto proto = dyn_cast<ProtocolDecl>(requirement->getDeclContext());
3035+
if (proto && proto->isDistributedActor()) {
3036+
// The requirement was declared in a DistributedActor constrained proto.
3037+
//
3038+
// This means casting up to this `P` won't "strip off" the "distributed-ness"
3039+
// of the type, and all call-sites will be checking distributed isolation.
3040+
//
3041+
// This means that we can actually allow these specific requirements,
3042+
// to be witnessed without the distributed keyword (!), but they won't be
3043+
// possible to be called unless:
3044+
// - from inside the distributed actor (self),
3045+
// - on a known-to-be-local distributed actor reference.
3046+
//
3047+
// This allows us to implement protocols where a local distributed actor
3048+
// registers "call me when something happens", and that call can be
3049+
// expressed as non-distributed function which we are guaranteed to be
3050+
// able to call, since the whenLocal will give us access to this actor as
3051+
// known-to-be-local, so we can invoke this method.
3052+
3053+
// If the requirement is distributed, we still need to require it on the witness though.
3054+
// We DO allow a non-distributed requirement to be witnessed here though!
3055+
if (isDistributedDecl(requirement) &&
3056+
!isDistributedDecl(witness))
3057+
missingOptions |= MissingFlags::WitnessDistributed;
3058+
} else {
3059+
// The protocol requirement comes from a normal (non-distributed actor)
3060+
// protocol; so the only witnesses allowed are such that we can witness
3061+
// them using a distributed, or nonisolated functions.
3062+
3063+
// If we're coming from a non-distributed requirement,
3064+
// then the requirement must be 'throws' to accommodate failure.
3065+
if (!isDistributedDecl(requirement) && !isThrowsDecl(requirement))
3066+
missingOptions |= MissingFlags::RequirementThrows;
3067+
3068+
// If the requirement is distributed, we require a distributed witness
3069+
if (!isDistributedDecl(witness) &&
3070+
(isDistributedDecl(requirement) || !missingOptions))
3071+
missingOptions |= MissingFlags::WitnessDistributed;
3072+
}
30403073
}
30413074

30423075
// If we aren't missing anything, do a Sendable check and move on.

test/Distributed/distributed_protocol_isolation.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,56 @@ func testAsyncThrowsAll(p: AsyncThrowsAll,
170170
_ = try await pp.maybe(param: "", int: 0)
171171
}
172172

173+
// ==== -----------------------------------------------------------------------
174+
// MARK: Distributed actor protocols can have non-dist requirements
175+
176+
protocol TerminationWatchingA {
177+
func terminated(a: String) async
178+
// expected-note@-1{{mark the protocol requirement 'terminated(a:)' 'throws' to allow actor-isolated conformances}}
179+
}
180+
181+
protocol TerminationWatchingDA: DistributedActor {
182+
func terminated(da: String) async
183+
// expected-note@-1{{distributed actor-isolated instance method 'terminated(da:)' declared here}}
184+
// expected-note@-2{{distributed actor-isolated instance method 'terminated(da:)' declared here}}
185+
}
186+
187+
actor A_TerminationWatchingA: TerminationWatchingA {
188+
func terminated(a: String) { } // ok, since: actor -> implicitly async
189+
}
190+
func test_watching_A(a: A_TerminationWatchingA) async throws {
191+
await a.terminated(a: "normal")
192+
}
193+
194+
distributed actor DA_TerminationWatchingA: TerminationWatchingA {
195+
func terminated(a: String) { }
196+
// expected-error@-1{{distributed actor-isolated instance method 'terminated(a:)' cannot be used to satisfy nonisolated protocol requirement}}
197+
// expected-note@-2{{add 'nonisolated' to 'terminated(a:)' to make this instance method not isolated to the actor}}
198+
}
199+
200+
distributed actor DA_TerminationWatchingDA: TerminationWatchingDA {
201+
distributed func test() {}
202+
func terminated(da: String) { }
203+
// expected-note@-1{{distributed actor-isolated instance method 'terminated(da:)' declared here}}
204+
}
205+
206+
func test_watchingDA(da: DA_TerminationWatchingDA) async throws {
207+
try await da.test() // ok
208+
da.terminated(da: "the terminated func is not distributed") // expected-error{{only 'distributed' instance methods can be called on a potentially remote distributed actor}}
209+
}
210+
211+
func test_watchingDA<WDA: TerminationWatchingDA>(da: WDA) async throws {
212+
try await da.terminated(da: "the terminated func is not distributed")
213+
// expected-error@-1{{only 'distributed' instance methods can be called on a potentially remote distributed actor}}
214+
// expected-warning@-2{{no calls to throwing functions occur within 'try' expression}}
215+
}
216+
217+
func test_watchingDA_any(da: any TerminationWatchingDA) async throws {
218+
try await da.terminated(da: "the terminated func is not distributed")
219+
// expected-error@-1{{only 'distributed' instance methods can be called on a potentially remote distributed actor}}
220+
// expected-warning@-2{{no calls to throwing functions occur within 'try' expression}}
221+
}
222+
173223
// ==== ------------------------------------------------------------------------
174224
// MARK: Error cases
175225

0 commit comments

Comments
 (0)