Skip to content

Commit 3837661

Browse files
committed
[Isolated conformances] Allow conformance isolation to differ from the type's
With the move to explicitly specifying the global actor for an isolated conformance, we can now have conformances whose isolation differs from that of the type, including having actors with global-actor-isolated conformances. Introduce this generalization to match the proposal, and update/add tests accordingly.
1 parent 4f24761 commit 3837661

File tree

8 files changed

+129
-64
lines changed

8 files changed

+129
-64
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8319,31 +8319,28 @@ ERROR(attr_abi_incompatible_with_silgen_name,none,
83198319
//===----------------------------------------------------------------------===//
83208320
// MARK: Isolated conformances
83218321
//===----------------------------------------------------------------------===//
8322-
ERROR(isolated_conformance_not_global_actor_isolated,none,
8323-
"isolated conformance is only permitted on global-actor-isolated types",
8324-
())
83258322
ERROR(isolated_conformance_experimental_feature,none,
83268323
"isolated conformances require experimental feature "
83278324
" 'IsolatedConformances'", ())
83288325
ERROR(nonisolated_conformance_depends_on_isolated_conformance,none,
8329-
"conformance of %0 to %1 depends on %2 conformance of %3 to %4; mark it as 'isolated'",
8330-
(Type, DeclName, ActorIsolation, Type, DeclName))
8326+
"conformance of %0 to %1 depends on %2 conformance of %3 to %4; mark it as '%5'",
8327+
(Type, DeclName, ActorIsolation, Type, DeclName, StringRef))
83318328
ERROR(isolated_conformance_mismatch_with_associated_isolation,none,
83328329
"%0 conformance of %1 to %2 cannot depend on %3 conformance of %4 to %5",
83338330
(ActorIsolation, Type, DeclName, ActorIsolation, Type, DeclName))
83348331
NOTE(add_isolated_to_conformance,none,
83358332
"add '%0' to the %1 conformance to restrict it to %2 code",
83368333
(StringRef, DeclName, ActorIsolation))
83378334
ERROR(isolated_conformance_with_sendable,none,
8338-
"isolated conformance of %0 to %1 cannot be used to satisfy conformance "
8335+
"%4 conformance of %0 to %1 cannot be used to satisfy conformance "
83398336
"requirement for a %select{`Sendable`|`SendableMetatype`}2 type "
8340-
"parameter %3", (Type, DeclName, bool, Type))
8337+
"parameter %3", (Type, DeclName, bool, Type, ActorIsolation))
83418338
ERROR(isolated_conformance_with_sendable_simple,none,
8342-
"isolated conformance of %0 to %1 cannot be used to satisfy conformance "
8343-
"requirement for a `Sendable` type parameter ",
8344-
(Type, DeclName))
8339+
"%2 conformance of %0 to %1 cannot be used to satisfy "
8340+
"conformance requirement for a `Sendable` type parameter ",
8341+
(Type, DeclName, ActorIsolation))
83458342
ERROR(isolated_conformance_wrong_domain,none,
8346-
"%0 isolated conformance of %1 to %2 cannot be used in %3 context",
8343+
"%0 conformance of %1 to %2 cannot be used in %3 context",
83478344
(ActorIsolation, Type, DeclName, ActorIsolation))
83488345

83498346
//===----------------------------------------------------------------------===//

lib/Sema/CSDiagnostics.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "CSDiagnostics.h"
1818
#include "MiscDiagnostics.h"
19+
#include "TypeCheckConcurrency.h"
1920
#include "TypeCheckProtocol.h"
2021
#include "TypeCheckType.h"
2122
#include "TypoCorrection.h"
@@ -9566,7 +9567,8 @@ bool IncorrectInlineArrayLiteralCount::diagnoseAsError() {
95669567
bool DisallowedIsolatedConformance::diagnoseAsError() {
95679568
emitDiagnostic(diag::isolated_conformance_with_sendable_simple,
95689569
conformance->getType(),
9569-
conformance->getProtocol()->getName());
9570+
conformance->getProtocol()->getName(),
9571+
getConformanceIsolation(conformance));
95709572

95719573
auto selectedOverload = getCalleeOverloadChoiceIfAvailable(getLocator());
95729574
if (!selectedOverload)

lib/Sema/TypeCheckGeneric.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// This file implements support for generics.
1414
//
1515
//===----------------------------------------------------------------------===//
16+
#include "TypeCheckConcurrency.h"
1617
#include "TypeCheckProtocol.h"
1718
#include "TypeCheckType.h"
1819
#include "TypeChecker.h"
@@ -936,7 +937,8 @@ void TypeChecker::diagnoseRequirementFailure(
936937
reqFailureInfo
937938
.IsolatedConformanceProto->isSpecificProtocol(
938939
KnownProtocolKind::SendableMetatype),
939-
req.getFirstType());
940+
req.getFirstType(),
941+
getConformanceIsolation(isolatedConformance));
940942
diagnosticNote = diag::type_does_not_inherit_or_conform_requirement;
941943
break;
942944
}

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2542,20 +2542,11 @@ checkIndividualConformance(NormalProtocolConformance *conformance) {
25422542
ComplainLoc, diag::unchecked_conformance_not_special, ProtoType);
25432543
}
25442544

2545-
// Complain if the conformance is isolated but the conforming type is
2546-
// not global-actor-isolated.
2547-
if (conformance->isGlobalActorIsolated()) {
2548-
auto enclosingNominal = DC->getSelfNominalTypeDecl();
2549-
if (!enclosingNominal ||
2550-
!getActorIsolation(enclosingNominal).isGlobalActor()) {
2551-
Context.Diags.diagnose(
2552-
ComplainLoc, diag::isolated_conformance_not_global_actor_isolated);
2553-
}
2554-
2555-
if (!Context.LangOpts.hasFeature(Feature::IsolatedConformances)) {
2556-
Context.Diags.diagnose(
2557-
ComplainLoc, diag::isolated_conformance_experimental_feature);
2558-
}
2545+
// Complain if the global-actor-isolated conformances are not enabled.
2546+
if (conformance->isGlobalActorIsolated() &&
2547+
!Context.LangOpts.hasFeature(Feature::IsolatedConformances)) {
2548+
Context.Diags.diagnose(
2549+
ComplainLoc, diag::isolated_conformance_experimental_feature);
25592550
}
25602551

25612552
bool allowImpliedConditionalConformance = false;
@@ -3330,14 +3321,6 @@ static bool hasExplicitGlobalActorAttr(ValueDecl *decl) {
33303321
return !globalActorAttr->first->isImplicit();
33313322
}
33323323

3333-
/// Determine whether the given actor isolation matches that of the enclosing
3334-
/// type.
3335-
static bool isolationMatchesEnclosingType(
3336-
ActorIsolation isolation, NominalTypeDecl *nominal) {
3337-
auto nominalIsolation = getActorIsolation(nominal);
3338-
return isolation == nominalIsolation;
3339-
}
3340-
33413324
std::optional<ActorIsolation>
33423325
ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
33433326
ValueDecl *witness,
@@ -3400,13 +3383,10 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
34003383

34013384
return std::nullopt;
34023385
case ActorReferenceResult::EntersActor:
3403-
// If the conformance itself is isolated, and the witness isolation
3404-
// matches the enclosing type's isolation, treat this as being in the
3405-
// same concurrency domain.
3386+
// If the conformance itself is isolated to the same isolation domain as
3387+
// the witness, treat this as being in the same concurrency domain.
34063388
if (Conformance->isGlobalActorIsolated() &&
3407-
refResult.isolation.isGlobalActor() &&
3408-
isolationMatchesEnclosingType(
3409-
refResult.isolation, DC->getSelfNominalTypeDecl())) {
3389+
refResult.isolation == getConformanceIsolation(Conformance)) {
34103390
sameConcurrencyDomain = true;
34113391
isIsolatedConformance = true;
34123392
}
@@ -5244,14 +5224,19 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
52445224
// If the conformance we're checking isn't isolated at all, it
52455225
// needs "isolated".
52465226
if (!conformance->isGlobalActorIsolated()) {
5227+
auto isolation = getConformanceIsolation(isolatedConformance);
5228+
std::string globalActorStr = "@" +
5229+
isolation.getGlobalActor().getString();
52475230
ctx.Diags.diagnose(
52485231
conformance->getLoc(),
52495232
diag::nonisolated_conformance_depends_on_isolated_conformance,
52505233
typeInContext, conformance->getProtocol()->getName(),
52515234
getConformanceIsolation(isolatedConformance),
52525235
isolatedConformance->getType(),
5253-
isolatedConformance->getProtocol()->getName()
5254-
).fixItInsert(conformance->getProtocolNameLoc(), "isolated ");
5236+
isolatedConformance->getProtocol()->getName(),
5237+
globalActorStr
5238+
).fixItInsert(conformance->getProtocolNameLoc(),
5239+
globalActorStr + " ");
52555240

52565241
return true;
52575242
}

test/Concurrency/Runtime/isolated_conformance.swift

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ protocol P {
1616
func f()
1717
}
1818

19-
@MainActor
20-
class MyClass: @MainActor P {
19+
protocol Q {
20+
func g()
21+
}
22+
23+
nonisolated class MyClass: @MainActor P {
2124
func f() {
2225
print("MyClass.f()")
2326

@@ -26,6 +29,22 @@ class MyClass: @MainActor P {
2629
}
2730
}
2831

32+
actor SomeActor { }
33+
34+
@globalActor
35+
struct SomeGlobalActor {
36+
static let shared = SomeActor()
37+
}
38+
39+
extension MyClass: @SomeGlobalActor Q {
40+
@SomeGlobalActor func g() {
41+
print("MyClass.g()")
42+
43+
// Make sure we're on this actor.
44+
SomeGlobalActor.shared.assumeIsolated { _ in }
45+
}
46+
}
47+
2948
struct Wrapper<T> {
3049
var wrapped: T
3150
}
@@ -37,6 +56,13 @@ extension Wrapper: P where T: P {
3756
}
3857
}
3958

59+
extension Wrapper: Q where T: Q {
60+
func g() {
61+
print("Wrapper for ", terminator: "")
62+
wrapped.g()
63+
}
64+
}
65+
4066
@available(SwiftStdlib 5.9, *)
4167
struct WrapMany<each T> {
4268
var wrapped: (repeat each T)
@@ -49,12 +75,21 @@ extension WrapMany: P where repeat each T: P {
4975
}
5076
}
5177

52-
extension Int: P {
78+
@available(SwiftStdlib 5.9, *)
79+
extension WrapMany: Q where repeat each T: Q {
80+
func g() {
81+
print("Wrapper for many")
82+
}
83+
}
84+
85+
extension Int: P, Q {
5386
func f() { }
87+
func g() { }
5488
}
5589

56-
extension String: P {
90+
extension String: P, Q {
5791
func f() { }
92+
func g() { }
5893
}
5994

6095
func tryCastToP(_ value: any Sendable) -> Bool {
@@ -67,12 +102,22 @@ func tryCastToP(_ value: any Sendable) -> Bool {
67102
return false
68103
}
69104

105+
func tryCastToQ(_ value: any Sendable) -> Bool {
106+
if let q = value as? any Q {
107+
q.g()
108+
return true
109+
}
110+
111+
print("Conformance did not match")
112+
return false
113+
}
114+
70115
// CHECK: Testing on the main actor
71116
// CHECK-NEXT: MyClass.f()
72117
// CHECK-NEXT: Wrapper for MyClass.f()
73118
print("Testing on the main actor")
74-
let mc = MyClass()
75-
let wrappedMC = Wrapper(wrapped: mc)
119+
nonisolated let mc = MyClass()
120+
nonisolated let wrappedMC = Wrapper(wrapped: mc)
76121
precondition(tryCastToP(mc))
77122
precondition(tryCastToP(wrappedMC))
78123

@@ -96,13 +141,34 @@ await Task.detached { @MainActor in
96141

97142
}.value
98143

144+
// CHECK: Testing a separate task on a different global actor
145+
// CHECK-NEXT: MyClass.g()
146+
// CHECK-NEXT: Wrapper for MyClass.g()
147+
print("Testing a separate task on a different global actor")
148+
await Task.detached { @SomeGlobalActor in
149+
precondition(tryCastToQ(mc))
150+
precondition(tryCastToQ(wrappedMC))
151+
152+
if #available(SwiftStdlib 5.9, *) {
153+
let wrappedMany = WrapMany(wrapped: (17, mc, "Pack"))
154+
precondition(tryCastToQ(wrappedMany))
155+
}
156+
157+
// Not on the main actor any more.
158+
precondition(!tryCastToP(mc))
159+
precondition(!tryCastToP(wrappedMC))
160+
}.value
161+
99162
// CHECK: Testing a separate task off the main actor
100163
print("Testing a separate task off the main actor")
101164
await Task.detached {
102165
if #available(SwiftStdlib 6.2, *) {
103166
precondition(!tryCastToP(mc))
104167
precondition(!tryCastToP(wrappedMC))
105168

169+
precondition(!tryCastToQ(mc))
170+
precondition(!tryCastToQ(wrappedMC))
171+
106172
let wrappedMany = WrapMany(wrapped: (17, mc, "Pack"))
107173
precondition(!tryCastToP(wrappedMany))
108174
} else {

test/Concurrency/isolated_conformance.swift

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// REQUIRES: concurrency
55

66
protocol P {
7-
func f() // expected-note 2{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}}
7+
func f() // expected-note{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}}
88
}
99

1010
// ----------------------------------------------------------------------------
@@ -22,23 +22,24 @@ class CWithNonIsolated: P {
2222
actor SomeActor { }
2323

2424
// Isolated conformances need a global-actor-constrained type.
25-
class CNonIsolated: @MainActor P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}}
25+
class CNonIsolated: @MainActor P {
2626
func f() { }
2727
}
2828

29-
extension SomeActor: @MainActor P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}}
30-
nonisolated func f() { }
29+
extension SomeActor: @MainActor P {
30+
@MainActor func f() { }
3131
}
3232

3333
@globalActor
3434
struct SomeGlobalActor {
3535
static let shared = SomeActor()
3636
}
3737

38-
// Isolation of the conformance needs to match that of the enclosing type.
38+
// Isolation of the conformance can be different from that of the enclosing
39+
// type, so long as the witnesses match up.
3940
@MainActor
4041
class CMismatchedIsolation: @SomeGlobalActor P {
41-
@SomeGlobalActor func f() { } // expected-error{{global actor 'SomeGlobalActor'-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}}
42+
@SomeGlobalActor func f() { }
4243
}
4344

4445
@MainActor
@@ -52,7 +53,7 @@ protocol Q {
5253
associatedtype A: P
5354
}
5455

55-
// expected-error@+2{{conformance of 'SMissingIsolation' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as 'isolated'}}{{27-27=isolated }}
56+
// expected-error@+2{{conformance of 'SMissingIsolation' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as '@MainActor'}}{{27-27=@MainActor }}
5657
@MainActor
5758
struct SMissingIsolation: Q {
5859
typealias A = C
@@ -62,7 +63,7 @@ struct PWrapper<T: P>: P {
6263
func f() { }
6364
}
6465

65-
// expected-error@+2{{conformance of 'SMissingIsolationViaWrapper' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as 'isolated'}}
66+
// expected-error@+2{{conformance of 'SMissingIsolationViaWrapper' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as '@MainActor'}}
6667
@MainActor
6768
struct SMissingIsolationViaWrapper: Q {
6869
typealias A = PWrapper<C>
@@ -111,18 +112,30 @@ func acceptSendableP<T: Sendable & P>(_: T) { }
111112
// expected-note@-1{{'acceptSendableP' declared here}}
112113

113114
func acceptSendableMetaP<T: SendableMetatype & P>(_: T) { }
114-
// expected-note@-1{{'acceptSendableMetaP' declared here}}
115+
// expected-note@-1 3{{'acceptSendableMetaP' declared here}}
115116

116117
@MainActor
117118
func testIsolationConformancesInCall(c: C) {
118119
acceptP(c) // okay
119120

120-
acceptSendableP(c) // expected-error{{isolated conformance of 'C' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}}
121+
acceptSendableP(c) // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}}
121122
acceptSendableMetaP(c) // expected-error{{isolated conformance of 'C' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}}
122123
}
123124

125+
@MainActor
126+
func testIsolatedConformancesOfActor(a: SomeActor) {
127+
acceptP(a)
128+
acceptSendableMetaP(a) // expected-error{{main actor-isolated conformance of 'SomeActor' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}}
129+
}
130+
131+
@SomeGlobalActor
132+
func testIsolatedConformancesOfOtherGlobalActor(c: CMismatchedIsolation) {
133+
acceptP(c)
134+
acceptSendableMetaP(c) // expected-error{{global actor 'SomeGlobalActor'-isolated conformance of 'CMismatchedIsolation' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}}
135+
}
136+
124137
func testIsolationConformancesFromOutside(c: C) {
125-
acceptP(c) // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
126-
let _: any P = c // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
127-
let _ = PWrapper<C>() // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
138+
acceptP(c) // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
139+
let _: any P = c // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
140+
let _ = PWrapper<C>() // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
128141
}

test/ModuleInterface/isolated_conformance.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public protocol MyProtocol {
1010
@MainActor
1111
public class MyClass { }
1212

13-
// CHECK: extension isolated_conformance.MyClass : @MainActor isolated_conformance.MyProtocol {
13+
// CHECK: extension isolated_conformance.MyClass : @{{.*}}MainActor isolated_conformance.MyProtocol {
1414
extension MyClass: @MainActor MyProtocol {
1515
@MainActor public func f() { }
1616
}

test/Serialization/isolated_conformance.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ func acceptMyProtocol(_: some MyProtocol) { }
1212

1313
nonisolated func f(mc: MyClass) {
1414
acceptMyProtocol(mc)
15-
// expected-error@-1{{main actor-isolated isolated conformance of 'MyClass' to 'MyProtocol' cannot be used in nonisolated context}}
15+
// expected-error@-1{{main actor-isolated conformance of 'MyClass' to 'MyProtocol' cannot be used in nonisolated context}}
1616
}

0 commit comments

Comments
 (0)