Skip to content

Commit 536b036

Browse files
authored
Merge pull request #40560 from DougGregor/conformance-cross-actor-sendable-checks
2 parents c37bfaf + 7db313b commit 536b036

File tree

4 files changed

+183
-5
lines changed

4 files changed

+183
-5
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2865,6 +2865,31 @@ static void emitDeclaredHereIfNeeded(DiagnosticEngine &diags,
28652865
diags.diagnose(value, diag::decl_declared_here, value->getName());
28662866
}
28672867

2868+
/// Determine if the witness can be made implicitly async.
2869+
static bool isValidImplicitAsync(ValueDecl *witness, ValueDecl *requirement) {
2870+
if (auto witnessFunc = dyn_cast<AbstractFunctionDecl>(witness)) {
2871+
if (auto requirementFunc = dyn_cast<AbstractFunctionDecl>(requirement)) {
2872+
return requirementFunc->hasAsync() &&
2873+
!(witnessFunc->hasThrows() && !requirementFunc->hasThrows());
2874+
}
2875+
2876+
return false;
2877+
}
2878+
2879+
if (auto witnessStorage = dyn_cast<AbstractStorageDecl>(witness)) {
2880+
if (auto requirementStorage = dyn_cast<AbstractStorageDecl>(requirement)) {
2881+
auto requirementAccessor = requirementStorage->getEffectfulGetAccessor();
2882+
if (!requirementAccessor || !requirementAccessor->hasAsync())
2883+
return false;
2884+
2885+
return witnessStorage->isLessEffectfulThan(
2886+
requirementStorage, EffectKind::Throws);
2887+
}
2888+
}
2889+
2890+
return false;
2891+
}
2892+
28682893
bool ConformanceChecker::checkActorIsolation(
28692894
ValueDecl *requirement, ValueDecl *witness) {
28702895
/// Retrieve a concrete witness for Sendable checking.
@@ -2991,9 +3016,15 @@ bool ConformanceChecker::checkActorIsolation(
29913016
// A synchronous actor function can witness an asynchronous protocol
29923017
// requirement, since calls "through" the protocol are always cross-actor,
29933018
// in which case the function becomes implicitly async.
3019+
//
3020+
// In this case, we're crossing actor boundaries so we need to
3021+
// perform Sendable checking.
29943022
if (witnessClass && witnessClass->isActor()) {
2995-
if (requirementFunc && requirementFunc->hasAsync() &&
2996-
(requirementFunc->hasThrows() == witnessFunc->hasThrows())) {
3023+
if (isValidImplicitAsync(witness, requirement)) {
3024+
diagnoseNonSendableTypesInReference(
3025+
getConcreteWitness(), DC, witness->getLoc(),
3026+
ConcurrentReferenceKind::CrossActor);
3027+
29973028
return false;
29983029
}
29993030
}

test/Concurrency/actor_isolation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -967,13 +967,13 @@ func testCrossActorProtocol<T: P>(t: T) async {
967967

968968
@available(SwiftStdlib 5.1, *)
969969
protocol Server {
970-
func send<Message: Codable>(message: Message) async throws -> String
970+
func send<Message: Codable & Sendable>(message: Message) async throws -> String
971971
}
972972

973973
@available(SwiftStdlib 5.1, *)
974974
actor MyServer : Server {
975975
// okay, asynchronously accessed from clients of the protocol
976-
func send<Message: Codable>(message: Message) throws -> String { "" }
976+
func send<Message: Codable & Sendable>(message: Message) throws -> String { "" }
977977
}
978978

979979
// ----------------------------------------------------------------------
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// RUN: %target-typecheck-verify-swift
2+
// REQUIRES: concurrency
3+
4+
@available(SwiftStdlib 5.1, *)
5+
class NotSendable { // expected-note 8{{class 'NotSendable' does not conform to the 'Sendable' protocol}}
6+
}
7+
8+
@available(SwiftStdlib 5.1, *)
9+
@available(*, unavailable)
10+
extension NotSendable: Sendable { }
11+
12+
@available(SwiftStdlib 5.1, *)
13+
protocol IsolatedWithNotSendableRequirements: Actor {
14+
func f() -> NotSendable
15+
var prop: NotSendable { get }
16+
}
17+
18+
// Okay, everything is isolated the same way
19+
@available(SwiftStdlib 5.1, *)
20+
actor A1: IsolatedWithNotSendableRequirements {
21+
func f() -> NotSendable { NotSendable() }
22+
var prop: NotSendable { NotSendable() }
23+
}
24+
25+
// Okay, sendable checking occurs when calling through the protocol
26+
// and also inside the bodies.
27+
@available(SwiftStdlib 5.1, *)
28+
actor A2: IsolatedWithNotSendableRequirements {
29+
nonisolated func f() -> NotSendable { NotSendable() }
30+
nonisolated var prop: NotSendable { NotSendable() }
31+
}
32+
33+
@available(SwiftStdlib 5.1, *)
34+
protocol AsyncProtocolWithNotSendable {
35+
func f() async -> NotSendable
36+
var prop: NotSendable { get async }
37+
}
38+
39+
// Sendable checking required because calls through protocol cross into the
40+
// actor's domain.
41+
@available(SwiftStdlib 5.1, *)
42+
actor A3: AsyncProtocolWithNotSendable {
43+
func f() async -> NotSendable { NotSendable() } // expected-warning{{cannot call function returning non-sendable type 'NotSendable' across actors}}
44+
45+
var prop: NotSendable { // expected-warning{{cannot use property 'prop' with a non-sendable type 'NotSendable' across actors}}
46+
get async {
47+
NotSendable()
48+
}
49+
}
50+
}
51+
52+
// Sendable checking required because calls through protocol cross into the
53+
// actor's domain.
54+
@available(SwiftStdlib 5.1, *)
55+
actor A4: AsyncProtocolWithNotSendable {
56+
func f() -> NotSendable { NotSendable() } // expected-warning{{cannot call function returning non-sendable type 'NotSendable' across actors}}
57+
58+
var prop: NotSendable { // expected-warning{{cannot use property 'prop' with a non-sendable type 'NotSendable' across actors}}
59+
get {
60+
NotSendable()
61+
}
62+
}
63+
}
64+
65+
// Sendable checking not required because we never cross into the actor's
66+
// domain.
67+
@available(SwiftStdlib 5.1, *)
68+
actor A5: AsyncProtocolWithNotSendable {
69+
nonisolated func f() async -> NotSendable { NotSendable() }
70+
71+
nonisolated var prop: NotSendable {
72+
get async {
73+
NotSendable()
74+
}
75+
}
76+
}
77+
78+
// Sendable checking not required because we never cross into the actor's
79+
// domain.
80+
@available(SwiftStdlib 5.1, *)
81+
actor A6: AsyncProtocolWithNotSendable {
82+
nonisolated func f() -> NotSendable { NotSendable() }
83+
84+
nonisolated var prop: NotSendable {
85+
get {
86+
NotSendable()
87+
}
88+
}
89+
}
90+
91+
@available(SwiftStdlib 5.1, *)
92+
protocol AsyncThrowingProtocolWithNotSendable {
93+
func f() async throws -> NotSendable
94+
var prop: NotSendable { get async throws }
95+
}
96+
97+
// Sendable checking required because calls through protocol cross into the
98+
// actor's domain.
99+
@available(SwiftStdlib 5.1, *)
100+
actor A7: AsyncThrowingProtocolWithNotSendable {
101+
func f() async -> NotSendable { NotSendable() } // expected-warning{{cannot call function returning non-sendable type 'NotSendable' across actors}}
102+
103+
var prop: NotSendable { // expected-warning{{cannot use property 'prop' with a non-sendable type 'NotSendable' across actors}}
104+
get async {
105+
NotSendable()
106+
}
107+
}
108+
}
109+
110+
// Sendable checking required because calls through protocol cross into the
111+
// actor's domain.
112+
@available(SwiftStdlib 5.1, *)
113+
actor A8: AsyncThrowingProtocolWithNotSendable {
114+
func f() -> NotSendable { NotSendable() } // expected-warning{{cannot call function returning non-sendable type 'NotSendable' across actors}}
115+
116+
var prop: NotSendable { // expected-warning{{cannot use property 'prop' with a non-sendable type 'NotSendable' across actors}}
117+
get {
118+
NotSendable()
119+
}
120+
}
121+
}
122+
123+
// Sendable checking not required because we never cross into the actor's
124+
// domain.
125+
@available(SwiftStdlib 5.1, *)
126+
actor A9: AsyncThrowingProtocolWithNotSendable {
127+
nonisolated func f() async -> NotSendable { NotSendable() }
128+
129+
nonisolated var prop: NotSendable {
130+
get async {
131+
NotSendable()
132+
}
133+
}
134+
}
135+
136+
// Sendable checking not required because we never cross into the actor's
137+
// domain.
138+
@available(SwiftStdlib 5.1, *)
139+
actor A10: AsyncThrowingProtocolWithNotSendable {
140+
nonisolated func f() -> NotSendable { NotSendable() }
141+
142+
nonisolated var prop: NotSendable {
143+
get {
144+
NotSendable()
145+
}
146+
}
147+
}

test/Distributed/distributed_protocol_isolation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ protocol Server {
129129
func send<Message: Codable>(message: Message) async throws -> String
130130
}
131131
actor MyServer : Server {
132-
func send<Message: Codable>(message: Message) throws -> String { "" } // okay, asynchronously accessed from clients of the protocol
132+
func send<Message: Codable>(message: Message) throws -> String { "" } // expected-warning{{cannot pass argument of non-sendable type 'Message' across actors}}
133133
}
134134

135135
protocol AsyncThrowsAll {

0 commit comments

Comments
 (0)