Skip to content

Commit 84cb3c8

Browse files
authored
Merge pull request swiftlang#79893 from simanerush/fix-explicit-nonisolated-proto-inference
[Concurrency] Look for explicit 'nonisolated' when getting isolation from protocol conformances.
2 parents ddf0e13 + 06be7bd commit 84cb3c8

File tree

2 files changed

+136
-4
lines changed

2 files changed

+136
-4
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5145,6 +5145,11 @@ getIsolationFromWitnessedRequirements(ValueDecl *value) {
51455145
std::tuple<ProtocolConformance *, ActorIsolation, ValueDecl *>;
51465146
SmallVector<IsolatedRequirement, 2> isolatedRequirements;
51475147
for (auto conformance : conformances) {
5148+
auto *implied =
5149+
conformance->getSourceKind() == ConformanceEntryKind::Implied
5150+
? conformance->getImplyingConformance()
5151+
: nullptr;
5152+
51485153
auto protocol = conformance->getProtocol();
51495154
for (auto found : protocol->lookupDirect(value->getName())) {
51505155
if (!isa<ProtocolDecl>(found->getDeclContext()))
@@ -5154,6 +5159,19 @@ getIsolationFromWitnessedRequirements(ValueDecl *value) {
51545159
if (!requirement || isa<TypeDecl>(requirement))
51555160
continue;
51565161

5162+
// The conformance implied by an explicitly stated nonisolated protocol
5163+
// makes all of the requirements nonisolated.
5164+
if (implied &&
5165+
implied->getSourceKind() == ConformanceEntryKind::Explicit) {
5166+
auto protocol = implied->getProtocol();
5167+
if (protocol->getAttrs().hasAttribute<NonisolatedAttr>()) {
5168+
isolatedRequirements.push_back(IsolatedRequirement{
5169+
implied, ActorIsolation::forNonisolated(/*unsafe=*/false),
5170+
requirement});
5171+
continue;
5172+
}
5173+
}
5174+
51575175
auto requirementIsolation = getActorIsolation(requirement);
51585176
switch (requirementIsolation) {
51595177
case ActorIsolation::ActorInstance:
@@ -5264,19 +5282,35 @@ getIsolationFromConformances(NominalTypeDecl *nominal) {
52645282
}
52655283

52665284
auto *proto = conformance->getProtocol();
5267-
switch (auto protoIsolation = getActorIsolation(proto)) {
5285+
auto inferredIsolation = getInferredActorIsolation(proto);
5286+
auto protoIsolation = inferredIsolation.isolation;
5287+
switch (protoIsolation) {
52685288
case ActorIsolation::ActorInstance:
52695289
case ActorIsolation::Unspecified:
5270-
case ActorIsolation::Nonisolated:
52715290
case ActorIsolation::CallerIsolationInheriting:
52725291
case ActorIsolation::NonisolatedUnsafe:
52735292
break;
5293+
case ActorIsolation::Nonisolated:
5294+
if (inferredIsolation.source.kind == IsolationSource::Kind::Explicit) {
5295+
if (!foundIsolation) {
5296+
// We found an explicitly 'nonisolated' protocol.
5297+
foundIsolation = {
5298+
protoIsolation,
5299+
IsolationSource(proto, IsolationSource::Conformance)};
5300+
}
5301+
continue;
5302+
} else {
5303+
break;
5304+
}
52745305

52755306
case ActorIsolation::Erased:
52765307
llvm_unreachable("protocol cannot have erased isolation");
52775308

52785309
case ActorIsolation::GlobalActor:
5279-
if (!foundIsolation) {
5310+
// If we encountered an explicit globally isolated conformance, allow it
5311+
// to override the nonisolated isolation kind.
5312+
if (!foundIsolation ||
5313+
conformance->getSourceKind() == ConformanceEntryKind::Explicit) {
52805314
foundIsolation = {
52815315
protoIsolation,
52825316
IsolationSource(proto, IsolationSource::Conformance)

test/Concurrency/nonisolated_rules.swift

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public struct PublicNonSendable {
6363
}
6464

6565

66-
nonisolated struct NonisolatedStruct: GloballyIsolated {
66+
nonisolated struct StructRemovesGlobalActor: GloballyIsolated {
6767
var x: NonSendable
6868
var y: Int = 1
6969

@@ -98,12 +98,110 @@ nonisolated struct S1: GloballyIsolated {
9898
// MARK: - Protocols
9999

100100
nonisolated protocol Refined: GloballyIsolated {}
101+
nonisolated protocol WhyNot {}
102+
103+
nonisolated protocol NonisolatedWithMembers {
104+
func test()
105+
}
101106

102107
struct A: Refined {
103108
var x: NonSendable
104109
init(x: NonSendable) {
105110
self.x = x // okay
106111
}
112+
113+
init() {
114+
self.x = NonSendable()
115+
}
116+
117+
func f() {}
118+
}
119+
120+
@MainActor protocol ExplicitGlobalActor: Refined {}
121+
122+
struct IsolatedA: ExplicitGlobalActor {
123+
// expected-note@+2 {{main actor isolation inferred from conformance to protocol 'ExplicitGlobalActor'}}
124+
// expected-note@+1 {{calls to instance method 'g()' from outside of its actor context are implicitly asynchronous}}
125+
func g() {}
126+
}
127+
128+
struct IsolatedB: Refined, ExplicitGlobalActor {
129+
// expected-note@+2 {{calls to instance method 'h()' from outside of its actor context are implicitly asynchronous}}
130+
// expected-note@+1 {{main actor isolation inferred from conformance to protocol 'ExplicitGlobalActor'}}
131+
func h() {}
132+
}
133+
134+
struct IsolatedC: WhyNot, GloballyIsolated {
135+
// expected-note@+2 {{calls to instance method 'k()' from outside of its actor context are implicitly asynchronous}}
136+
// expected-note@+1 {{main actor isolation inferred from conformance to protocol 'GloballyIsolated'}}
137+
func k() {}
138+
}
139+
140+
struct IsolatedCFlipped: GloballyIsolated, WhyNot {
141+
// expected-note@+2 {{calls to instance method 'k2()' from outside of its actor context are implicitly asynchronous}}
142+
// expected-note@+1 {{main actor isolation inferred from conformance to protocol 'GloballyIsolated'}}
143+
func k2() {}
144+
}
145+
146+
struct NonisolatedStruct {
147+
func callF() {
148+
return A().f() // okay, 'A' is non-isolated.
149+
}
150+
151+
// expected-note@+1 {{add '@MainActor' to make instance method 'callG()' part of global actor 'MainActor'}}
152+
func callG() {
153+
// expected-error@+1{{call to main actor-isolated instance method 'g()' in a synchronous nonisolated context}}
154+
return IsolatedA().g()
155+
}
156+
157+
// expected-note@+1 {{add '@MainActor' to make instance method 'callH()' part of global actor 'MainActor'}}
158+
func callH() {
159+
// expected-error@+1 {{call to main actor-isolated instance method 'h()' in a synchronous nonisolated context}}
160+
return IsolatedB().h()
161+
}
162+
163+
// expected-note@+1 {{add '@MainActor' to make instance method 'callK()' part of global actor 'MainActor'}}
164+
func callK() {
165+
// expected-error@+1 {{call to main actor-isolated instance method 'k()' in a synchronous nonisolated context}}
166+
return IsolatedC().k()
167+
}
168+
169+
// expected-note@+1 {{add '@MainActor' to make instance method 'callK2()' part of global actor 'MainActor'}}
170+
func callK2() {
171+
// expected-error@+1 {{call to main actor-isolated instance method 'k2()' in a synchronous nonisolated context}}
172+
return IsolatedCFlipped().k2()
173+
}
174+
}
175+
176+
@MainActor
177+
struct TestIsolated : NonisolatedWithMembers {
178+
var x: NonSendable // expected-note {{property declared here}}
179+
180+
// requirement behaves as if it's explicitly `nonisolated` which gets inferred onto the witness
181+
func test() {
182+
_ = x // expected-error {{main actor-isolated property 'x' can not be referenced from a nonisolated context}}
183+
}
184+
}
185+
186+
@MainActor
187+
protocol Root {
188+
func testRoot()
189+
}
190+
191+
nonisolated protocol Child : Root {
192+
func testChild()
193+
}
194+
195+
struct TestDifferentLevels : Child {
196+
func testRoot() {}
197+
func testChild() {}
198+
func testNonWitness() {}
199+
}
200+
201+
nonisolated func testRequirementsOnMultipleNestingLevels(t: TestDifferentLevels) {
202+
t.testRoot() // okay
203+
t.testChild() // okay
204+
t.testNonWitness() // okay
107205
}
108206

109207
// MARK: - Extensions

0 commit comments

Comments
 (0)