Skip to content

Commit 80b6fbd

Browse files
authored
Merge pull request swiftlang#38186 from DougGregor/actors-and-global-actors
Semantic tweaks to (global) actors to bring them in line with the accepted proposals
2 parents 87cde88 + e595b4f commit 80b6fbd

24 files changed

+138
-55
lines changed

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,44 @@ _**Note:** This is in reverse chronological order, so newer entries are added to
66
Swift 5.5
77
---------
88

9+
* [SE-0313][]:
10+
11+
A type can be defined as a global actor. Global actors extend the notion
12+
of actor isolation outside of a single actor type, so that global state
13+
(and the functions that access it) can benefit from actor isolation,
14+
even if the state and functions are scattered across many different
15+
types, functions and modules. Global actors make it possible to safely
16+
work with global variables in a concurrent program, as well as modeling
17+
other global program constraints such as code that must only execute on
18+
the "main thread" or "UI thread". A new global actor can be defined with
19+
the `globalActor` attribute:
20+
21+
```swift
22+
@globalActor
23+
struct DatabaseActor {
24+
actor ActorType { }
25+
26+
static let shared: ActorType = ActorType()
27+
}
28+
```
29+
30+
Global actor types can be used as custom attributes on various declarations,
31+
which ensures that those declarations are only accessed on the actor described
32+
by the global actor's `shared` instance. For example:
33+
34+
```swift
35+
@DatabaseActor func queryDB(query: Query) throws -> QueryResult
36+
37+
func runQuery(queryString: String) async throws -> QueryResult {
38+
let query = try Query(parsing: queryString)
39+
return try await queryDB(query: query) // 'await' because this implicitly hops to DatabaseActor.shared
40+
}
41+
```
42+
43+
The concurrency library defines one global actor, `MainActor`, which
44+
represents the main thread of execution. It should be used for any code that
45+
must execute on the main thread, e.g., for updating UI.
46+
947
* [SE-0313][]:
1048

1149
Declarations inside an actor that would normally be actor-isolated can
@@ -8525,6 +8563,7 @@ Swift 1.0
85258563
[SE-0306]: <https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md>
85268564
[SE-0310]: <https://github.com/apple/swift-evolution/blob/main/proposals/0310-effectful-readonly-properties.md>
85278565
[SE-0313]: <https://github.com/apple/swift-evolution/blob/main/proposals/0313-actor-isolation-control.md>
8566+
[SE-0316]: <https://github.com/apple/swift-evolution/blob/main/proposals/0316-global-actors.md>
85288567

85298568
[SR-75]: <https://bugs.swift.org/browse/SR-75>
85308569
[SR-106]: <https://bugs.swift.org/browse/SR-106>

include/swift/AST/Attr.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(isolated, Isolated,
581581
103)
582582

583583
SIMPLE_DECL_ATTR(globalActor, GlobalActor,
584-
OnClass | OnStruct | OnEnum | ConcurrencyOnly |
584+
OnClass | OnStruct | OnEnum |
585585
ABIStableToAdd | ABIBreakingToRemove |
586586
APIStableToAdd | APIBreakingToRemove,
587587
104)

include/swift/AST/Decl.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,10 @@ class ValueDecl : public Decl {
23742374
/// Note whether this declaration is known to be exposed to Objective-C.
23752375
void setIsObjC(bool Value);
23762376

2377+
/// Is this declaration semantically 'final', meaning that the type checker
2378+
/// should treat it as final even if the ABI does not?
2379+
bool isSemanticallyFinal() const;
2380+
23772381
/// Is this declaration 'final'?
23782382
bool isFinal() const;
23792383

@@ -7556,7 +7560,8 @@ inline bool Decl::isPotentiallyOverridable() const {
75567560
isa<SubscriptDecl>(this) ||
75577561
isa<FuncDecl>(this) ||
75587562
isa<DestructorDecl>(this)) {
7559-
return getDeclContext()->getSelfClassDecl();
7563+
auto classDecl = getDeclContext()->getSelfClassDecl();
7564+
return classDecl && !classDecl->isActor();
75607565
} else {
75617566
return false;
75627567
}

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4608,6 +4608,8 @@ ERROR(global_actor_on_local_variable,none,
46084608
"local variable %0 cannot have a global actor", (DeclName))
46094609
ERROR(global_actor_non_unsafe_init,none,
46104610
"global actor attribute %0 argument can only be '(unsafe)'", (Type))
4611+
ERROR(global_actor_non_final_class,none,
4612+
"non-final class %0 cannot be a global actor", (DeclName))
46114613

46124614
ERROR(actor_isolation_multiple_attr,none,
46134615
"%0 %1 has multiple actor-isolation attributes ('%2' and '%3')",

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2828,7 +2828,7 @@ AnyFunctionType::Param swift::computeSelfParam(AbstractFunctionDecl *AFD,
28282828
if (Ctx.isSwiftVersionAtLeast(5)) {
28292829
if (wantDynamicSelf && CD->isConvenienceInit())
28302830
if (auto *classDecl = selfTy->getClassOrBoundGenericClass())
2831-
if (!classDecl->isFinal())
2831+
if (!classDecl->isSemanticallyFinal())
28322832
isDynamicSelf = true;
28332833
}
28342834
} else if (isa<DestructorDecl>(AFD)) {

lib/AST/Decl.cpp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2868,6 +2868,23 @@ void ValueDecl::setIsObjC(bool value) {
28682868
LazySemanticInfo.isObjC = value;
28692869
}
28702870

2871+
bool ValueDecl::isSemanticallyFinal() const {
2872+
// Actor types are semantically final.
2873+
if (auto classDecl = dyn_cast<ClassDecl>(this)) {
2874+
if (classDecl->isActor())
2875+
return true;
2876+
}
2877+
2878+
// As are members of actor types.
2879+
if (auto classDecl = getDeclContext()->getSelfClassDecl()) {
2880+
if (classDecl->isActor())
2881+
return true;
2882+
}
2883+
2884+
// For everything else, the same as 'final'.
2885+
return isFinal();
2886+
}
2887+
28712888
bool ValueDecl::isFinal() const {
28722889
return evaluateOrDefault(getASTContext().evaluator,
28732890
IsFinalRequest { const_cast<ValueDecl *>(this) },
@@ -3212,13 +3229,13 @@ bool ValueDecl::shouldHideFromEditor() const {
32123229
static AccessLevel getMaximallyOpenAccessFor(const ValueDecl *decl) {
32133230
// Non-final classes are considered open to @testable importers.
32143231
if (auto cls = dyn_cast<ClassDecl>(decl)) {
3215-
if (!cls->isFinal())
3232+
if (!cls->isSemanticallyFinal())
32163233
return AccessLevel::Open;
32173234

32183235
// Non-final overridable class members are considered open to
32193236
// @testable importers.
32203237
} else if (decl->isPotentiallyOverridable()) {
3221-
if (!cast<ValueDecl>(decl)->isFinal())
3238+
if (!cast<ValueDecl>(decl)->isSemanticallyFinal())
32223239
return AccessLevel::Open;
32233240
}
32243241

@@ -3332,9 +3349,6 @@ AccessLevel ValueDecl::getFormalAccess() const {
33323349
}
33333350

33343351
bool ValueDecl::hasOpenAccess(const DeclContext *useDC) const {
3335-
assert(isa<ClassDecl>(this) || isa<ConstructorDecl>(this) ||
3336-
isPotentiallyOverridable());
3337-
33383352
AccessLevel access =
33393353
getAdjustedFormalAccess(this, useDC,
33403354
/*treatUsableFromInlineAsPublic*/false);

lib/IDE/CodeCompletion.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5422,7 +5422,7 @@ class CompletionOverrideLookup : public swift::VisibleDeclConsumer {
54225422
switch (Reason) {
54235423
case DeclVisibilityKind::MemberOfProtocolConformedToByCurrentNominal:
54245424
case DeclVisibilityKind::MemberOfProtocolDerivedByCurrentNominal:
5425-
if (!C->isFinal())
5425+
if (!C->isSemanticallyFinal())
54265426
needRequired = true;
54275427
break;
54285428
case DeclVisibilityKind::MemberOfSuper:
@@ -5458,7 +5458,7 @@ class CompletionOverrideLookup : public swift::VisibleDeclConsumer {
54585458
if (D->shouldHideFromEditor())
54595459
return;
54605460

5461-
if (D->isFinal())
5461+
if (D->isSemanticallyFinal())
54625462
return;
54635463

54645464
bool hasIntroducer = hasFuncIntroducer ||

lib/Sema/CSSimplify.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7720,7 +7720,7 @@ static bool isNonFinalClass(Type type) {
77207720
type = dynamicSelf->getSelfType();
77217721

77227722
if (auto classDecl = type->getClassOrBoundGenericClass())
7723-
return !classDecl->isFinal();
7723+
return !classDecl->isSemanticallyFinal();
77247724

77257725
if (auto archetype = type->getAs<ArchetypeType>())
77267726
if (auto super = archetype->getSuperclass())

lib/Sema/CodeSynthesisDistributedActor.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,6 @@ createDistributedActor_init_local(ClassDecl *classDecl,
223223
initDecl->setSynthesized();
224224
initDecl->setBodySynthesizer(&createBody_DistributedActor_init_transport);
225225

226-
// This constructor is 'required', all distributed actors MUST invoke it.
227-
auto *reqAttr = new (C) RequiredAttr(/*IsImplicit*/true);
228-
initDecl->getAttrs().add(reqAttr);
229-
230226
auto *nonIsoAttr = new (C) NonisolatedAttr(/*IsImplicit*/true);
231227
initDecl->getAttrs().add(nonIsoAttr);
232228

@@ -350,9 +346,6 @@ createDistributedActor_init_resolve(ClassDecl *classDecl,
350346
initDecl->setSynthesized();
351347
initDecl->setBodySynthesizer(&createDistributedActor_init_resolve_body);
352348

353-
// This constructor is 'required', all distributed actors MUST have it.
354-
initDecl->getAttrs().add(new (C) RequiredAttr(/*IsImplicit*/true));
355-
356349
auto *nonIsoAttr = new (C) NonisolatedAttr(/*IsImplicit*/true);
357350
initDecl->getAttrs().add(nonIsoAttr);
358351

lib/Sema/DerivedConformanceCodable.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1789,7 +1789,7 @@ static ValueDecl *deriveDecodable_init(DerivedConformance &derived) {
17891789
}
17901790

17911791
// This constructor should be marked as `required` for non-final classes.
1792-
if (classDecl && !classDecl->isFinal()) {
1792+
if (classDecl && !classDecl->isSemanticallyFinal()) {
17931793
auto *reqAttr = new (C) RequiredAttr(/*IsImplicit=*/true);
17941794
initDecl->getAttrs().add(reqAttr);
17951795
}

lib/Sema/DerivedConformances.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ bool DerivedConformance::checkAndDiagnoseDisallowedContext(
545545
// A non-final class can't have an protocol-witnesss initializer in an
546546
// extension.
547547
if (auto CD = dyn_cast<ClassDecl>(Nominal)) {
548-
if (!CD->isFinal() && isa<ConstructorDecl>(synthesizing) &&
548+
if (!CD->isSemanticallyFinal() && isa<ConstructorDecl>(synthesizing) &&
549549
isa<ExtensionDecl>(ConformanceDecl)) {
550550
ConformanceDecl->diagnose(
551551
diag::cannot_synthesize_init_in_extension_of_nonfinal,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,9 @@ void AttributeChecker::visitAccessControlAttr(AccessControlAttr *attr) {
799799
}
800800

801801
if (attr->getAccess() == AccessLevel::Open) {
802-
if (!isa<ClassDecl>(D) && !D->isPotentiallyOverridable() &&
802+
auto classDecl = dyn_cast<ClassDecl>(D);
803+
if (!(classDecl && !classDecl->isActor()) &&
804+
!D->isPotentiallyOverridable() &&
803805
!attr->isInvalid()) {
804806
diagnose(attr->getLocation(), diag::access_control_open_bad_decl)
805807
.fixItReplace(attr->getRange(), "public");
@@ -2085,7 +2087,8 @@ void AttributeChecker::visitRequiredAttr(RequiredAttr *attr) {
20852087
return;
20862088
}
20872089
// Only classes can have required constructors.
2088-
if (parentTy->getClassOrBoundGenericClass()) {
2090+
if (parentTy->getClassOrBoundGenericClass() &&
2091+
!parentTy->getClassOrBoundGenericClass()->isActor()) {
20892092
// The constructor must be declared within the class itself.
20902093
// FIXME: Allow an SDK overlay to add a required initializer to a class
20912094
// defined in Objective-C
@@ -4489,7 +4492,7 @@ IndexSubset *DifferentiableAttributeTypeCheckRequest::evaluate(
44894492
if (diagnoseDynamicSelfResult) {
44904493
// Diagnose class initializers in non-final classes.
44914494
if (isa<ConstructorDecl>(original)) {
4492-
if (!classDecl->isFinal()) {
4495+
if (!classDecl->isSemanticallyFinal()) {
44934496
diags.diagnose(
44944497
attr->getLocation(),
44954498
diag::differentiable_attr_nonfinal_class_init_unsupported,
@@ -4790,7 +4793,7 @@ static bool typeCheckDerivativeAttr(ASTContext &Ctx, Decl *D,
47904793
if (diagnoseDynamicSelfResult) {
47914794
// Diagnose class initializers in non-final classes.
47924795
if (isa<ConstructorDecl>(originalAFD)) {
4793-
if (!classDecl->isFinal()) {
4796+
if (!classDecl->isSemanticallyFinal()) {
47944797
diags.diagnose(attr->getLocation(),
47954798
diag::derivative_attr_nonfinal_class_init_unsupported,
47964799
classDecl->getDeclaredInterfaceType());

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,17 @@ VarDecl *GlobalActorInstanceRequest::evaluate(
142142
return nullptr;
143143
}
144144

145+
// Non-final classes cannot be global actors.
146+
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
147+
if (!classDecl->isSemanticallyFinal()) {
148+
nominal->diagnose(diag::global_actor_non_final_class, nominal->getName())
149+
.highlight(globalActorAttr->getRangeWithAt());
150+
}
151+
}
152+
145153
// Global actors have a static property "shared" that provides an actor
146-
// instance. The value must
154+
// instance. The value must be of Actor type, which is validated by
155+
// conformance to the 'GlobalActor' protocol.
147156
SmallVector<ValueDecl *, 4> decls;
148157
nominal->lookupQualified(
149158
nominal, DeclNameRef(ctx.Id_shared), NL_QualifiedDefault, decls);
@@ -3545,7 +3554,7 @@ bool swift::checkSendableConformance(
35453554

35463555
if (classDecl) {
35473556
// An non-final class cannot conform to `Sendable`.
3548-
if (!classDecl->isFinal()) {
3557+
if (!classDecl->isSemanticallyFinal()) {
35493558
classDecl->diagnose(diag::concurrent_value_nonfinal_class,
35503559
classDecl->getName())
35513560
.limitBehavior(behavior);

lib/Sema/TypeCheckDecl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,7 @@ IsDynamicRequest::evaluate(Evaluator &evaluator, ValueDecl *decl) const {
871871
return true;
872872

873873
// The presence of 'final' blocks the inference of 'dynamic'.
874-
if (decl->isFinal())
874+
if (decl->isSemanticallyFinal())
875875
return false;
876876

877877
// Types are never 'dynamic'.
@@ -1868,7 +1868,7 @@ FunctionOperatorRequest::evaluate(Evaluator &evaluator, FuncDecl *FD) const {
18681868
if (dc->isTypeContext()) {
18691869
if (auto classDecl = dc->getSelfClassDecl()) {
18701870
// For a class, we also need the function or class to be 'final'.
1871-
if (!classDecl->isFinal() && !FD->isFinal() &&
1871+
if (!classDecl->isSemanticallyFinal() && !FD->isFinal() &&
18721872
FD->getStaticLoc().isValid() &&
18731873
FD->getStaticSpelling() != StaticSpellingKind::KeywordStatic) {
18741874
FD->diagnose(diag::nonfinal_operator_in_class,

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,7 +1028,7 @@ static void checkOverrideAccessControl(ValueDecl *baseDecl, ValueDecl *decl,
10281028
} else if (baseHasOpenAccess &&
10291029
classDecl->hasOpenAccess(dc) &&
10301030
decl->getFormalAccess() < AccessLevel::Public &&
1031-
!decl->isFinal()) {
1031+
!decl->isSemanticallyFinal()) {
10321032
{
10331033
auto diag = diags.diagnose(decl, diag::override_not_accessible,
10341034
/*setter*/false,
@@ -1150,7 +1150,7 @@ bool OverrideMatcher::checkOverride(ValueDecl *baseDecl,
11501150
if (decl->getASTContext().isSwiftVersionAtLeast(5) &&
11511151
baseDecl->getInterfaceType()->hasDynamicSelfType() &&
11521152
!decl->getInterfaceType()->hasDynamicSelfType() &&
1153-
!classDecl->isFinal()) {
1153+
!classDecl->isSemanticallyFinal()) {
11541154
diags.diagnose(decl, diag::override_dynamic_self_mismatch);
11551155
diags.diagnose(baseDecl, diag::overridden_here);
11561156
}
@@ -1942,7 +1942,7 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
19421942
}
19431943

19441944
// The overridden declaration cannot be 'final'.
1945-
if (base->isFinal() && !isAccessor) {
1945+
if (base->isSemanticallyFinal() && !isAccessor) {
19461946
// Use a special diagnostic for overriding an actor's unownedExecutor
19471947
// method. TODO: only if it's implicit? But then we need to
19481948
// propagate implicitness in module interfaces.

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,7 @@ swift::matchWitness(WitnessChecker::RequirementEnvironmentCache &reqEnvCache,
947947
ClassDecl *covariantSelf = nullptr;
948948
if (witness->getDeclContext()->getExtendedProtocolDecl()) {
949949
if (auto *classDecl = dc->getSelfClassDecl()) {
950-
if (!classDecl->isFinal()) {
950+
if (!classDecl->isSemanticallyFinal()) {
951951
// If the requirement's type does not involve any associated types,
952952
// we use a class-constrained generic parameter as the 'Self' type
953953
// in the witness thunk.
@@ -3238,7 +3238,7 @@ printRequirementStub(ValueDecl *Requirement, DeclContext *Adopter,
32383238
Type AdopterTy, SourceLoc TypeLoc, raw_ostream &OS) {
32393239
if (isa<ConstructorDecl>(Requirement)) {
32403240
if (auto CD = Adopter->getSelfClassDecl()) {
3241-
if (!CD->isFinal() && isa<ExtensionDecl>(Adopter)) {
3241+
if (!CD->isSemanticallyFinal() && isa<ExtensionDecl>(Adopter)) {
32423242
// In this case, user should mark class as 'final' or define
32433243
// 'required' initializer directly in the class definition.
32443244
return false;
@@ -4180,7 +4180,7 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
41804180
}
41814181

41824182
if (auto *classDecl = Adoptee->getClassOrBoundGenericClass()) {
4183-
if (!classDecl->isFinal()) {
4183+
if (!classDecl->isSemanticallyFinal()) {
41844184
checkNonFinalClassWitness(requirement, witness);
41854185
}
41864186
}
@@ -5201,7 +5201,7 @@ TypeChecker::couldDynamicallyConformToProtocol(Type type, ProtocolDecl *Proto,
52015201

52025202
// A non-final class might have a subclass that conforms to the protocol.
52035203
if (auto *classDecl = type->getClassOrBoundGenericClass()) {
5204-
if (!classDecl->isFinal())
5204+
if (!classDecl->isSemanticallyFinal())
52055205
return true;
52065206
}
52075207

test/Concurrency/global_actor_inference.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,10 @@ actor GenericSuper<T> {
175175

176176
actor GenericSub<T> : GenericSuper<[T]> { // expected-error{{actor types do not support inheritance}}
177177
override func method() { } // expected-note {{calls to instance method 'method()' from outside of its actor context are implicitly asynchronous}}
178-
// expected-error@-1{{actor-isolated instance method 'method()' has different actor isolation from global actor 'GenericGlobalActor<[T]>'-isolated overridden declaration}}
178+
// expected-error@-1{{instance method overrides a 'final' instance method}}
179179

180-
@GenericGlobalActor<T> override func method2() { } // expected-error{{global actor 'GenericGlobalActor<T>'-isolated instance method 'method2()' has different actor isolation from global actor 'GenericGlobalActor<[T]>'-isolated overridden declaration}}
181-
nonisolated override func method3() { } // expected-error{{nonisolated instance method 'method3()' has different actor isolation from global actor 'GenericGlobalActor<[T]>'-isolated overridden declaration}}
180+
@GenericGlobalActor<T> override func method2() { } // expected-error{{instance method overrides a 'final' instance method}}
181+
nonisolated override func method3() { } // expected-error{{instance method overrides a 'final' instance method}}
182182

183183
@OtherGlobalActor func testMethod() {
184184
method() // expected-error{{actor-isolated instance method 'method()' can not be referenced from global actor 'OtherGlobalActor'}}

test/Distributed/actor_protocols.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ actor A2: DistributedActor {
4949
fatalError()
5050
}
5151

52-
required init(transport: ActorTransport) {
52+
init(transport: ActorTransport) {
5353
fatalError()
5454
}
55-
required init(resolve address: ActorAddress, using transport: ActorTransport) throws {
55+
init(resolve address: ActorAddress, using transport: ActorTransport) throws {
5656
fatalError()
5757
}
5858
}

0 commit comments

Comments
 (0)