Skip to content

Commit

Permalink
[Concurrency] Add globalActor attribute.
Browse files Browse the repository at this point in the history
The globalActor attribute indicates that a particular type describes a
global actor. Global actors allow the notion of actor state isolation
to be spread across various declarations throughout a program, rather
than being centered around a single actor class. There are useful
primarily for existing notions such as "main thread" or subsystems
accessed through global/singleton state.
  • Loading branch information
DougGregor committed Oct 9, 2020
1 parent a665ba6 commit 98903b7
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 0 deletions.
6 changes: 6 additions & 0 deletions include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,12 @@ SIMPLE_DECL_ATTR(actorIndependent, ActorIndependent,
APIStableToAdd | APIBreakingToRemove,
103)

SIMPLE_DECL_ATTR(globalActor, GlobalActor,
OnClass | OnStruct | OnEnum | ConcurrencyOnly |
ABIStableToAdd | ABIBreakingToRemove |
APIStableToAdd | APIBreakingToRemove,
104)

#undef TYPE_ATTR
#undef DECL_ATTR_ALIAS
#undef CONTEXTUAL_DECL_ATTR_ALIAS
Expand Down
14 changes: 14 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3172,6 +3172,20 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {

void synthesizeSemanticMembersIfNeeded(DeclName member);

/// Retrieves the static 'shared' property of a global actor type, which
/// is used to extract the actor instance.
///
/// \returns the static 'shared' property for a global actor, or \c nullptr
/// for types that are not global actors.
VarDecl *getGlobalActorInstance() const;

/// Whether this type is a global actor, which can be used as an
/// attribute to decorate declarations for inclusion in the actor-isolated
/// state denoted by this type.
bool isGlobalActor() const {
return getGlobalActorInstance() != nullptr;
}

// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) {
return D->getKind() >= DeclKind::First_NominalTypeDecl &&
Expand Down
16 changes: 16 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4205,6 +4205,22 @@ ERROR(enqueue_partial_task_not_in_context,none,
"'enqueue(partialTask:)' can only be implemented in the definition of "
"actor class %0", (Type))

ERROR(global_actor_missing_shared,none,
"global actor %0 requires a static property 'shared' that produces an "
"actor instance", (Identifier))
NOTE(global_actor_shared_not_static,none,
"'shared' property in global actor is not 'static'", ())
NOTE(global_actor_shared_inaccessible,none,
"'shared' property has more restrictive access (%0) than its global actor "
"(%1)",
(StringRef, StringRef))
NOTE(global_actor_shared_constrained_extension,none,
"'shared' property in global actor cannot be in a constrained extension",
())
NOTE(global_actor_shared_non_actor_type,none,
"'shared' property type %0 does not conform to the 'Actor' protocol",
(Type))

//------------------------------------------------------------------------------
// MARK: Type Check Types
//------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ IDENTIFIER(Selector)
IDENTIFIER(self)
IDENTIFIER(Self)
IDENTIFIER(setObject)
IDENTIFIER(shared)
IDENTIFIER(simd)
IDENTIFIER(storage)
IDENTIFIER(stringValue)
Expand Down
22 changes: 22 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,28 @@ class IsActorRequest :
bool isCached() const { return true; }
};

/// Retrieve the static "shared" property within a global actor that provides
/// the actor instance representing the global actor.
///
/// Global actors can be applied to a declaration to indicate that the
/// declaration operations on state that is protected by the global actor.
class GlobalActorInstanceRequest :
public SimpleRequest<GlobalActorInstanceRequest,
VarDecl *(NominalTypeDecl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

VarDecl *evaluate(Evaluator &evaluator, NominalTypeDecl *nominal) const;

public:
// Caching
bool isCached() const { return true; }
};

/// Determine the actor isolation for the given declaration.
class ActorIsolationRequest :
public SimpleRequest<ActorIsolationRequest,
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ SWIFT_REQUEST(TypeChecker, CanBeAsyncHandlerRequest, bool(FuncDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, IsActorRequest, bool(ClassDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, GlobalActorInstanceRequest,
VarDecl *(NominalTypeDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ActorIsolationRequest,
ActorIsolationState(ValueDecl *),
Cached, NoLocationInfo)
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4044,6 +4044,13 @@ void NominalTypeDecl::synthesizeSemanticMembersIfNeeded(DeclName member) {
}
}

VarDecl *NominalTypeDecl::getGlobalActorInstance() const {
auto mutableThis = const_cast<NominalTypeDecl *>(this);
return evaluateOrDefault(getASTContext().evaluator,
GlobalActorInstanceRequest{mutableThis},
nullptr);
}

ClassDecl::ClassDecl(SourceLoc ClassLoc, Identifier Name, SourceLoc NameLoc,
MutableArrayRef<TypeLoc> Inherited,
GenericParamList *GenericParams, DeclContext *Parent)
Expand Down
8 changes: 8 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,14 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
return;
}
}

void visitGlobalActorAttr(GlobalActorAttr *attr) {
auto nominal = dyn_cast<NominalTypeDecl>(D);
if (!nominal)
return; // already diagnosed

(void)nominal->isGlobalActor();
}
};
} // end anonymous namespace

Expand Down
123 changes: 123 additions & 0 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,129 @@ bool IsActorRequest::evaluate(
return actorAttr != nullptr;
}

static bool isDeclNotAsAccessibleAsParent(ValueDecl *decl,
NominalTypeDecl *parent) {
return decl->getFormalAccess() <
std::min(parent->getFormalAccess(), AccessLevel::Public);
}

VarDecl *GlobalActorInstanceRequest::evaluate(
Evaluator &evaluator, NominalTypeDecl *nominal) const {
auto globalActorAttr = nominal->getAttrs().getAttribute<GlobalActorAttr>();
if (!globalActorAttr)
return nullptr;

// Ensure that the actor protocol has been loaded.
ASTContext &ctx = nominal->getASTContext();
auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
if (!actorProto) {
nominal->diagnose(diag::concurrency_lib_missing, "Actor");
return nullptr;
}

// Global actors have a static property "shared" that provides an actor
// instance. The value must
SmallVector<ValueDecl *, 4> decls;
nominal->lookupQualified(
nominal, DeclNameRef(ctx.Id_shared), NL_QualifiedDefault, decls);
VarDecl *sharedVar = nullptr;
llvm::TinyPtrVector<VarDecl *> candidates;
for (auto decl : decls) {
auto var = dyn_cast<VarDecl>(decl);
if (!var)
continue;

auto varDC = var->getDeclContext();
if (var->isStatic() &&
!isDeclNotAsAccessibleAsParent(var, nominal) &&
!(isa<ExtensionDecl>(varDC) &&
cast<ExtensionDecl>(varDC)->isConstrainedExtension()) &&
TypeChecker::conformsToProtocol(
varDC->mapTypeIntoContext(var->getValueInterfaceType()),
actorProto, nominal)) {
sharedVar = var;
break;
}

candidates.push_back(var);
}

// If we found a suitable candidate, we're done.
if (sharedVar)
return sharedVar;

// Complain about the lack of a suitable 'shared' property.
{
auto primaryDiag = nominal->diagnose(
diag::global_actor_missing_shared, nominal->getName());

// If there were no candidates, provide a Fix-It with a prototype.
if (candidates.empty() && nominal->getBraces().Start.isValid()) {
// Figure out the indentation we need.
SourceLoc sharedInsertionLoc = Lexer::getLocForEndOfToken(
ctx.SourceMgr, nominal->getBraces().Start);

StringRef extraIndent;
StringRef currentIndent = Lexer::getIndentationForLine(
ctx.SourceMgr, sharedInsertionLoc, &extraIndent);
std::string stubIndent = (currentIndent + extraIndent).str();

// From the string to add the declaration.
std::string sharedDeclString = "\n" + stubIndent;
if (nominal->getFormalAccess() >= AccessLevel::Public)
sharedDeclString += "public ";

sharedDeclString += "static let shared = <#actor instance#>";

primaryDiag.fixItInsert(sharedInsertionLoc, sharedDeclString);
}
}

// Remark about all of the candidates that failed (and why).
for (auto candidate : candidates) {
if (!candidate->isStatic()) {
candidate->diagnose(diag::global_actor_shared_not_static)
.fixItInsert(candidate->getAttributeInsertionLoc(true), "static ");
continue;
}

if (isDeclNotAsAccessibleAsParent(candidate, nominal)) {
AccessLevel needAccessLevel = std::min(
nominal->getFormalAccess(), AccessLevel::Public);
auto diag = candidate->diagnose(
diag::global_actor_shared_inaccessible,
getAccessLevelSpelling(candidate->getFormalAccess()),
getAccessLevelSpelling(needAccessLevel));
if (auto attr = candidate->getAttrs().getAttribute<AccessControlAttr>()) {
if (needAccessLevel == AccessLevel::Internal) {
diag.fixItRemove(attr->getRange());
} else {
diag.fixItReplace(
attr->getRange(), getAccessLevelSpelling(needAccessLevel));
}
} else {
diag.fixItInsert(
candidate->getAttributeInsertionLoc(true),
getAccessLevelSpelling(needAccessLevel));
}
continue;
}

if (auto ext = dyn_cast<ExtensionDecl>(candidate->getDeclContext())) {
if (ext->isConstrainedExtension()) {
candidate->diagnose(diag::global_actor_shared_constrained_extension);
continue;
}
}

Type varType = candidate->getDeclContext()->mapTypeIntoContext(
candidate->getValueInterfaceType());
candidate->diagnose(diag::global_actor_shared_non_actor_type, varType);
}

return nullptr;
}

namespace {

/// The isolation restriction in effect for a given declaration that is
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,7 @@ namespace {
UNINTERESTING_ATTR(OriginallyDefinedIn)
UNINTERESTING_ATTR(Actor)
UNINTERESTING_ATTR(ActorIndependent)
UNINTERESTING_ATTR(GlobalActor)
#undef UNINTERESTING_ATTR

void visitAvailableAttr(AvailableAttr *attr) {
Expand Down
45 changes: 45 additions & 0 deletions test/attr/global_actor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// RUN: %target-swift-frontend -typecheck -verify %s -enable-experimental-concurrency
// REQUIRES: concurrency

import _Concurrency

actor class SomeActor { }

// Well-formed global actor.
@globalActor
struct GA1 {
static let shared = SomeActor()
}

// Ill-formed global actors.
@globalActor
open class GA2 { // expected-error{{global actor 'GA2' requires a static property 'shared' that produces an actor instance}}{{17-17=\n public static let shared = <#actor instance#>}}
}

@globalActor
struct GA3 { // expected-error{{global actor 'GA3' requires a static property 'shared' that produces an actor instance}}
let shared = SomeActor() // expected-note{{'shared' property in global actor is not 'static'}}{{3-3=static }}
}

@globalActor
struct GA4 { // expected-error{{global actor 'GA4' requires a static property 'shared' that produces an actor instance}}
private static let shared = SomeActor() // expected-note{{'shared' property has more restrictive access (private) than its global actor (internal)}}{{3-11=}}
}

@globalActor
open class GA5 { // expected-error{{global actor 'GA5' requires a static property 'shared' that produces an actor instance}}
static let shared = SomeActor() // expected-note{{'shared' property has more restrictive access (internal) than its global actor (public)}}{{3-3=public}}
}

@globalActor
struct GA6<T> { // expected-error{{global actor 'GA6' requires a static property 'shared' that produces an actor instance}}
}

extension GA6 where T: Equatable {
static var shared: SomeActor { SomeActor() } // expected-note{{'shared' property in global actor cannot be in a constrained extension}}
}

@globalActor
class GA7 { // expected-error{{global actor 'GA7' requires a static property 'shared' that produces an actor instance}}
static let shared = 5 // expected-note{{'shared' property type 'Int' does not conform to the 'Actor' protocol}}
}

0 comments on commit 98903b7

Please sign in to comment.