Skip to content

Commit 5b0f7f7

Browse files
authored
Merge pull request #73146 from simanerush/nonisolated-value-type-6.0
[6.0🍒][Concurrency] Improvements to the concurrency model for stored properties of globally-isolated `Sendable` value types and globally-isolated function types.
2 parents 4d3307a + da3cf4d commit 5b0f7f7

11 files changed

+123
-56
lines changed

include/swift/AST/Types.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3634,9 +3634,7 @@ class AnyFunctionType : public TypeBase {
36343634
return getExtInfo().isNoEscape();
36353635
}
36363636

3637-
bool isSendable() const {
3638-
return getExtInfo().isSendable();
3639-
}
3637+
bool isSendable() const;
36403638

36413639
bool isAsync() const { return getExtInfo().isAsync(); }
36423640

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ EXPERIMENTAL_FEATURE(CImplementation, true)
378378
// Enable the stdlib @DebugDescription macro.
379379
EXPERIMENTAL_FEATURE(DebugDescriptionMacro, true)
380380

381+
// Enable usability improvements for global-actor-isolated types.
382+
EXPERIMENTAL_FEATURE(GlobalActorIsolatedTypesUsability, false)
383+
381384
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
382385
#undef EXPERIMENTAL_FEATURE
383386
#undef UPCOMING_FEATURE

lib/AST/FeatureSet.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,10 @@ UNINTERESTING_FEATURE(CImplementation)
702702

703703
UNINTERESTING_FEATURE(DebugDescriptionMacro)
704704

705+
static bool usesFeatureGlobalActorIsolatedTypesUsability(Decl *decl) {
706+
return false;
707+
}
708+
705709
// ----------------------------------------------------------------------------
706710
// MARK: - FeatureSet
707711
// ----------------------------------------------------------------------------

lib/AST/Type.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3895,6 +3895,15 @@ Type AnyFunctionType::getThrownError() const {
38953895
}
38963896
}
38973897

3898+
bool AnyFunctionType::isSendable() const {
3899+
auto &ctx = getASTContext();
3900+
if (ctx.LangOpts.hasFeature(Feature::GlobalActorIsolatedTypesUsability)) {
3901+
// Global-actor-isolated function types are implicitly Sendable.
3902+
return getExtInfo().isSendable() || getIsolation().isGlobalActor();
3903+
}
3904+
return getExtInfo().isSendable();
3905+
}
3906+
38983907
Type AnyFunctionType::getGlobalActor() const {
38993908
switch (getKind()) {
39003909
case TypeKind::Function:

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -503,9 +503,26 @@ static bool varIsSafeAcrossActors(const ModuleDecl *fromModule,
503503
VarDecl *var,
504504
const ActorIsolation &varIsolation,
505505
ActorReferenceResult::Options &options) {
506-
// must be immutable
507-
if (!var->isLet())
506+
507+
bool accessWithinModule =
508+
(fromModule == var->getDeclContext()->getParentModule());
509+
510+
if (!var->isLet()) {
511+
ASTContext &ctx = var->getASTContext();
512+
if (ctx.LangOpts.hasFeature(Feature::GlobalActorIsolatedTypesUsability)) {
513+
// A mutable storage of a value type accessed from within the module is
514+
// okay.
515+
if (dyn_cast_or_null<StructDecl>(var->getDeclContext()->getAsDecl()) &&
516+
!var->isStatic() &&
517+
var->hasStorage() &&
518+
var->getTypeInContext()->isSendableType() &&
519+
accessWithinModule) {
520+
return true;
521+
}
522+
}
523+
// Otherwise, must be immutable.
508524
return false;
525+
}
509526

510527
switch (varIsolation) {
511528
case ActorIsolation::Nonisolated:
@@ -538,9 +555,6 @@ static bool varIsSafeAcrossActors(const ModuleDecl *fromModule,
538555
return false;
539556
}
540557

541-
bool accessWithinModule =
542-
(fromModule == var->getDeclContext()->getParentModule());
543-
544558
// If the type is not 'Sendable', it's unsafe
545559
if (!var->getTypeInContext()->isSendableType()) {
546560
// Compiler versions <= 5.10 treated this variable as nonisolated,
@@ -4181,15 +4195,21 @@ bool ActorIsolationChecker::mayExecuteConcurrentlyWith(
41814195
if (useIsolation == defIsolation)
41824196
return false;
41834197

4198+
auto &ctx = useContext->getASTContext();
4199+
bool regionIsolationEnabled =
4200+
ctx.LangOpts.hasFeature(Feature::RegionBasedIsolation);
4201+
4202+
// Globally-isolated closures may never be executed concurrently.
4203+
if (ctx.LangOpts.hasFeature(Feature::GlobalActorIsolatedTypesUsability) &&
4204+
regionIsolationEnabled && useIsolation.isGlobalActor())
4205+
return false;
4206+
41844207
// If the local function is not Sendable, its isolation differs
41854208
// from that of the context, and both contexts are actor isolated,
41864209
// then capturing non-Sendable values allows the closure to stash
41874210
// those values into actor isolated state. The original context
41884211
// may also stash those values into isolated state, enabling concurrent
41894212
// access later on.
4190-
auto &ctx = useContext->getASTContext();
4191-
bool regionIsolationEnabled =
4192-
ctx.LangOpts.hasFeature(Feature::RegionBasedIsolation);
41934213
isolatedStateMayEscape =
41944214
(!regionIsolationEnabled &&
41954215
useIsolation.isActorIsolated() && defIsolation.isActorIsolated());

test/Concurrency/actor_isolation.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/OtherActors.swiftmodule -module-name OtherActors %S/Inputs/OtherActors.swift -disable-availability-checking
44

5-
// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify %s
6-
// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify -enable-upcoming-feature RegionBasedIsolation %s
5+
// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify -enable-experimental-feature GlobalActorIsolatedTypesUsability %s
6+
// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify -enable-upcoming-feature RegionBasedIsolation -enable-experimental-feature GlobalActorIsolatedTypesUsability %s
77

88
// REQUIRES: concurrency
99
// REQUIRES: asserts
@@ -156,7 +156,7 @@ func checkIsolationValueType(_ formance: InferredFromConformance,
156156
_ anno: NoGlobalActorValueType) async {
157157
// these still do need an await in Swift 5
158158
_ = await ext.point // expected-warning {{non-sendable type 'Point' in implicitly asynchronous access to main actor-isolated property 'point' cannot cross actor boundary}}
159-
_ = await formance.counter
159+
_ = formance.counter
160160
_ = await anno.point // expected-warning {{non-sendable type 'Point' in implicitly asynchronous access to global actor 'SomeGlobalActor'-isolated property 'point' cannot cross actor boundary}}
161161
// expected-warning@-1 {{non-sendable type 'NoGlobalActorValueType' passed in implicitly asynchronous call to global actor 'SomeGlobalActor'-isolated property 'point' cannot cross actor boundary}}
162162
_ = anno.counter // expected-warning {{non-sendable type 'NoGlobalActorValueType' passed in call to main actor-isolated property 'counter' cannot cross actor boundary}}
@@ -559,7 +559,7 @@ extension MyActor {
559559

560560
func testBadImplicitGlobalActorClosureCall() async {
561561
{ @MainActor in }() // expected-error{{expression is 'async' but is not marked with 'await'}}
562-
// expected-note@-1{{calls function of type '@MainActor () -> ()' from outside of its actor context are implicitly asynchronous}}
562+
// expected-note@-1{{calls function of type '@MainActor @Sendable () -> ()' from outside of its actor context are implicitly asynchronous}}
563563
}
564564

565565

test/Concurrency/actor_isolation_swift6.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// RUN: %target-swift-frontend -disable-availability-checking -swift-version 6 -emit-sil -o /dev/null -verify %s
1+
// RUN: %target-swift-frontend -disable-availability-checking -swift-version 6 -emit-sil -o /dev/null -verify -enable-experimental-feature GlobalActorIsolatedTypesUsability %s
22

33
// REQUIRES: concurrency
4+
// REQUIRES: asserts
45

56
final class ImmutablePoint: Sendable {
67
let x : Int = 0
@@ -55,13 +56,12 @@ func checkIsolationValueType(_ formance: InferredFromConformance,
5556
_ = await ext.point // expected-warning {{no 'async' operations occur within 'await' expression}}
5657
_ = await formance.counter // expected-warning {{no 'async' operations occur within 'await' expression}}
5758
_ = await anno.counter // expected-warning {{no 'async' operations occur within 'await' expression}}
58-
59-
// We could extend the 'nonisolated within the module' rule to vars
60-
// value types types if the property type is 'Sendable'.
59+
60+
// this does not need an await, since the property is 'Sendable' and of a
61+
// value type
6162
_ = anno.point
62-
// expected-error@-1 {{expression is 'async' but is not marked with 'await'}}
63-
// expected-note@-2 {{property access is 'async'}}
6463
_ = await anno.point
64+
// expected-warning@-1 {{no 'async' operations occur within 'await' expression}}
6565

6666
// these do need await, regardless of reference or value type
6767
_ = await (formance as any MainCounter).counter
Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-swift-frontend -disable-availability-checking -strict-concurrency=complete -parse-as-library %s -emit-sil -o /dev/null -verify
2-
// RUN: %target-swift-frontend -disable-availability-checking -strict-concurrency=complete -parse-as-library %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation
1+
// RUN: %target-swift-frontend -disable-availability-checking -strict-concurrency=complete -parse-as-library %s -emit-sil -o /dev/null -verify -enable-experimental-feature GlobalActorIsolatedTypesUsability
2+
// RUN: %target-swift-frontend -disable-availability-checking -strict-concurrency=complete -parse-as-library %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation -enable-experimental-feature GlobalActorIsolatedTypesUsability
33

44
// REQUIRES: concurrency
55
// REQUIRES: asserts
@@ -10,14 +10,32 @@ struct X1: Equatable, Hashable, Codable {
1010
let y: String
1111
}
1212

13-
// expected-error@+5 3{{main actor-isolated property 'y' can not be referenced from a non-isolated context}}
14-
// expected-note@+4{{in static method '==' for derived conformance to 'Equatable'}}
15-
// expected-error@+3{{main actor-isolated property 'y' can not be referenced from a non-isolated context}}
16-
// expected-note@+2{{in static method '==' for derived conformance to 'Equatable'}}
13+
// okay
1714
@MainActor
1815
struct X2: Equatable, Hashable, Codable {
1916
let x: Int
20-
var y: String // expected-note 4 {{property declared here}}
17+
var y: String
18+
}
19+
20+
class NonSendable {
21+
let x: Int
22+
23+
init(x: Int) {
24+
self.x = x
25+
}
26+
}
27+
28+
extension NonSendable: Equatable {
29+
static func == (lhs: NonSendable, rhs: NonSendable) -> Bool {
30+
return lhs.x == rhs.x
31+
}
32+
}
33+
34+
// expected-warning@+3 2{{main actor-isolated property 'x' can not be referenced from a non-isolated context}}
35+
// expected-note@+2 2{{in static method '==' for derived conformance to 'Equatable'}}
36+
@MainActor
37+
struct X2NonSendable: Equatable {
38+
let x: NonSendable // expected-note 2 {{property declared here}}
2139
}
2240

2341
@MainActor
@@ -26,12 +44,9 @@ enum X3: Hashable, Comparable, Codable {
2644
case b(Int)
2745
}
2846

29-
// expected-warning@+5{{main actor-isolated property 'y' can not be referenced from a non-isolated context}}
30-
// expected-note@+4{{in static method '==' for derived conformance to 'Equatable'}}
31-
// expected-warning@+3{{main actor-isolated property 'y' can not be referenced from a non-isolated context}}
32-
// expected-note@+2{{in static method '==' for derived conformance to 'Equatable'}}
47+
// okay
3348
@preconcurrency @MainActor
3449
struct X4: Equatable {
3550
let x: Int
36-
var y: String // expected-note 2 {{property declared here}}
51+
var y: String
3752
}

test/Concurrency/global_actor_inference.swift

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// RUN: %empty-directory(%t)
22

3-
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/other_global_actor_inference.swiftmodule -module-name other_global_actor_inference -strict-concurrency=complete %S/Inputs/other_global_actor_inference.swift
4-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -verify-additional-prefix minimal-targeted-
5-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=targeted -verify-additional-prefix minimal-targeted-
6-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -verify-additional-prefix complete-tns-
7-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation -verify-additional-prefix complete-tns-
3+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/other_global_actor_inference.swiftmodule -module-name other_global_actor_inference -strict-concurrency=complete %S/Inputs/other_global_actor_inference.swift -enable-experimental-feature GlobalActorIsolatedTypesUsability
4+
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -verify-additional-prefix minimal-targeted- -enable-experimental-feature GlobalActorIsolatedTypesUsability
5+
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=targeted -verify-additional-prefix minimal-targeted- -enable-experimental-feature GlobalActorIsolatedTypesUsability
6+
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -verify-additional-prefix complete-tns- -enable-experimental-feature GlobalActorIsolatedTypesUsability
7+
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation -verify-additional-prefix complete-tns- -enable-experimental-feature GlobalActorIsolatedTypesUsability
88

99
// REQUIRES: concurrency
1010
// REQUIRES: asserts
@@ -285,14 +285,14 @@ func barSync() {
285285

286286
@OtherGlobalActor
287287
struct Observed {
288-
var thing: Int = 0 { // expected-note {{property declared here}}
288+
var thing: Int = 0 {
289289
didSet {}
290290
willSet {}
291291
}
292292
}
293293

294-
func checkObserved(_ o: Observed) { // expected-note {{add '@OtherGlobalActor' to make global function 'checkObserved' part of global actor 'OtherGlobalActor'}}
295-
_ = o.thing // expected-error {{global actor 'OtherGlobalActor'-isolated property 'thing' can not be referenced from a non-isolated context}}
294+
func checkObserved(_ o: Observed) {
295+
_ = o.thing // okay
296296
}
297297

298298
// ----------------------------------------------------------------------
@@ -376,13 +376,13 @@ actor WrapperActor<Wrapped: Sendable> {
376376

377377
struct HasWrapperOnActor {
378378
@WrapperOnActor var synced: Int = 0
379-
// expected-note@-1 3{{property declared here}}
379+
// expected-note@-1 2{{property declared here}}
380380

381-
// expected-note@+1 3{{to make instance method 'testErrors()'}}
381+
// expected-note@+1 2{{to make instance method 'testErrors()'}}
382382
func testErrors() {
383383
_ = synced // expected-error{{main actor-isolated property 'synced' can not be referenced from a non-isolated context}}
384384
_ = $synced // expected-error{{global actor 'SomeGlobalActor'-isolated property '$synced' can not be referenced from a non-isolated context}}
385-
_ = _synced // expected-error{{global actor 'OtherGlobalActor'-isolated property '_synced' can not be referenced from a non-isolated context}}
385+
_ = _synced // okay
386386
}
387387

388388
@MainActor mutating func testOnMain() {
@@ -566,22 +566,21 @@ struct HasWrapperOnUnsafeActor {
566566
// expected-complete-tns-warning@-2 {{default initializer for 'HasWrapperOnUnsafeActor' cannot be both nonisolated and global actor 'OtherGlobalActor'-isolated; this is an error in the Swift 6 language mode}}
567567

568568
@WrapperOnUnsafeActor var synced: Int = 0 // expected-complete-tns-note 2 {{initializer for property '_synced' is global actor 'OtherGlobalActor'-isolated}}
569-
// expected-note @-1 3{{property declared here}}
570-
// expected-complete-tns-note @-2 3{{property declared here}}
569+
// expected-note @-1 2{{property declared here}}
570+
// expected-complete-tns-note @-2 2{{property declared here}}
571571

572572
func testUnsafeOkay() {
573-
// expected-complete-tns-note @-1 {{add '@OtherGlobalActor' to make instance method 'testUnsafeOkay()' part of global actor 'OtherGlobalActor'}}
574-
// expected-complete-tns-note @-2 {{add '@SomeGlobalActor' to make instance method 'testUnsafeOkay()' part of global actor 'SomeGlobalActor'}}
575-
// expected-complete-tns-note @-3 {{add '@MainActor' to make instance method 'testUnsafeOkay()' part of global actor 'MainActor'}}
573+
// expected-complete-tns-note @-1 {{add '@SomeGlobalActor' to make instance method 'testUnsafeOkay()' part of global actor 'SomeGlobalActor'}}
574+
// expected-complete-tns-note @-2 {{add '@MainActor' to make instance method 'testUnsafeOkay()' part of global actor 'MainActor'}}
576575
_ = synced // expected-complete-tns-warning {{main actor-isolated property 'synced' can not be referenced from a non-isolated context}}
577576
_ = $synced // expected-complete-tns-warning {{global actor 'SomeGlobalActor'-isolated property '$synced' can not be referenced from a non-isolated context}}
578-
_ = _synced // expected-complete-tns-warning {{global actor 'OtherGlobalActor'-isolated property '_synced' can not be referenced from a non-isolated context}}
577+
_ = _synced // okay
579578
}
580579

581580
nonisolated func testErrors() {
582581
_ = synced // expected-warning{{main actor-isolated property 'synced' can not be referenced from a non-isolated context}}
583582
_ = $synced // expected-warning{{global actor 'SomeGlobalActor'-isolated property '$synced' can not be referenced from a non-isolated context}}
584-
_ = _synced // expected-warning{{global actor 'OtherGlobalActor'-isolated property '_synced' can not be referenced from a non-isolated context}}
583+
_ = _synced // okay
585584
}
586585

587586
@MainActor mutating func testOnMain() {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-typecheck-verify-swift -strict-concurrency=complete -disable-availability-checking -enable-upcoming-feature RegionBasedIsolation -enable-experimental-feature GlobalActorIsolatedTypesUsability
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: asserts
5+
6+
func inferSendableFunctionType() {
7+
let closure: @MainActor () -> Void = {}
8+
9+
Task {
10+
await closure() // okay
11+
}
12+
}
13+
14+
class NonSendable {}
15+
16+
func allowNonSendableCaptures() {
17+
let nonSendable = NonSendable()
18+
let _: @MainActor () -> Void = {
19+
let _ = nonSendable // okay
20+
}
21+
}

test/Concurrency/sendable_keypaths.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift -enable-upcoming-feature InferSendableFromCaptures -strict-concurrency=complete
1+
// RUN: %target-typecheck-verify-swift -enable-upcoming-feature InferSendableFromCaptures -strict-concurrency=complete -enable-experimental-feature GlobalActorIsolatedTypesUsability
22

33
// REQUIRES: concurrency
44
// REQUIRES: asserts
@@ -147,8 +147,7 @@ func testGlobalActorIsolatedReferences() {
147147
subscript(v: Int) -> Bool { false }
148148
}
149149

150-
let dataKP = \Isolated.data
151-
// expected-warning@-1 {{cannot form key path to main actor-isolated property 'data'; this is an error in the Swift 6 language mode}}
150+
let dataKP = \Isolated.data // Ok
152151
let subscriptKP = \Isolated.[42]
153152
// expected-warning@-1 {{cannot form key path to main actor-isolated subscript 'subscript(_:)'; this is an error in the Swift 6 language mode}}
154153

@@ -158,8 +157,7 @@ func testGlobalActorIsolatedReferences() {
158157
// expected-warning@-1 {{type 'KeyPath<Isolated, Bool>' does not conform to the 'Sendable' protocol}}
159158

160159
func testNonIsolated() {
161-
_ = \Isolated.data
162-
// expected-warning@-1 {{cannot form key path to main actor-isolated property 'data'; this is an error in the Swift 6 language mode}}
160+
_ = \Isolated.data // Ok
163161
}
164162

165163
@MainActor func testIsolated() {

0 commit comments

Comments
 (0)