Skip to content

[SE-0316] Introduce the GlobalActor protocol to describe global actors. #37917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4578,22 +4578,6 @@ ERROR(concurrency_lib_missing,none,
ERROR(async_main_no_concurrency,none,
"'_Concurrency' module not imported, required for async main", ())

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))

ERROR(multiple_global_actors,none,
"declaration can not have multiple global actor attributes (%0 and %1)",
(Identifier, Identifier))
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 @@ -28,6 +28,7 @@ IDENTIFIER(allKeys)
IDENTIFIER(alloc)
IDENTIFIER(allocWithZone)
IDENTIFIER(allZeros)
IDENTIFIER(ActorType)
IDENTIFIER(Any)
IDENTIFIER(ArrayLiteralElement)
IDENTIFIER(atIndexedSubscript)
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownProtocols.def
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ PROTOCOL(SIMDScalar)
PROTOCOL(BinaryInteger)
PROTOCOL(RangeReplaceableCollection)
PROTOCOL(SerialExecutor)
PROTOCOL(GlobalActor)

PROTOCOL_(BridgedNSError)
PROTOCOL_(BridgedStoredNSError)
Expand Down
1 change: 1 addition & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
M = getLoadedModule(Id_Differentiation);
break;
case KnownProtocolKind::Actor:
case KnownProtocolKind::GlobalActor:
case KnownProtocolKind::AsyncSequence:
case KnownProtocolKind::AsyncIteratorProtocol:
case KnownProtocolKind::SerialExecutor:
Expand Down
5 changes: 5 additions & 0 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,11 @@ void NominalTypeDecl::prepareConformanceTable() const {
if (classDecl->isDistributedActor())
addSynthesized(KnownProtocolKind::DistributedActor);
}

// Global actors conform to the GlobalActor protocol.
if (mutableThis->getAttrs().hasAttribute<GlobalActorAttr>()) {
addSynthesized(KnownProtocolKind::GlobalActor);
}
}

bool NominalTypeDecl::lookupConformance(
Expand Down
1 change: 1 addition & 0 deletions lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5170,6 +5170,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
case KnownProtocolKind::Sendable:
case KnownProtocolKind::UnsafeSendable:
case KnownProtocolKind::RangeReplaceableCollection:
case KnownProtocolKind::GlobalActor:
return SpecialProtocol::None;
}

Expand Down
98 changes: 2 additions & 96 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,6 @@ bool IsDefaultActorRequest::evaluate(
return true;
}

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>();
Expand All @@ -147,106 +141,18 @@ VarDecl *GlobalActorInstanceRequest::evaluate(
return nullptr;
}

auto *module = nominal->getParentModule();

// 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, module)) {
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);
if (var->getDeclContext() == nominal && var->isStatic())
return var;
}

return nullptr;
Expand Down
37 changes: 0 additions & 37 deletions stdlib/public/Concurrency/Actor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,6 @@ public func _defaultActorDestroy(_ actor: AnyObject)
@usableFromInline
internal func _enqueueOnMain(_ job: UnownedJob)

/// A singleton actor whose executor is equivalent to the main
/// dispatch queue.
@available(SwiftStdlib 5.5, *)
@globalActor public final actor MainActor: SerialExecutor {
public static let shared = MainActor()

@inlinable
public nonisolated var unownedExecutor: UnownedSerialExecutor {
return asUnownedSerialExecutor()
}

@inlinable
public nonisolated func asUnownedSerialExecutor() -> UnownedSerialExecutor {
return UnownedSerialExecutor(ordinary: self)
}

@inlinable
public nonisolated func enqueue(_ job: UnownedJob) {
_enqueueOnMain(job)
}
}

// Used by the concurrency runtime
@available(SwiftStdlib 5.5, *)
extension SerialExecutor {
Expand All @@ -82,18 +60,3 @@ extension SerialExecutor {
return MainActor.shared.unownedExecutor
}
}

@available(SwiftStdlib 5.5, *)
extension MainActor {
/// Execute the given body closure on the main actor.
public static func run<T>(
resultType: T.Type = T.self,
body: @MainActor @Sendable () throws -> T
) async rethrows -> T {
@MainActor func runOnMain(body: @MainActor @Sendable () throws -> T) async rethrows -> T {
return try body()
}

return try await runOnMain(body: body)
}
}
2 changes: 2 additions & 0 deletions stdlib/public/Concurrency/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
AsyncThrowingFlatMapSequence.swift
AsyncThrowingMapSequence.swift
AsyncThrowingPrefixWhileSequence.swift
GlobalActor.swift
MainActor.swift
PartialAsyncTask.swift
SourceCompatibilityShims.swift
Task.cpp
Expand Down
52 changes: 52 additions & 0 deletions stdlib/public/Concurrency/GlobalActor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Swift

/// A type that represents a globally-unique actor that can be used to isolate
/// various declarations anywhere in the program.
///
/// A type that conforms to the `GlobalActor` protocol and is marked with the
/// the `@globalActor` attribute can be used as a custom attribute. Such types
/// are called global actor types, and can be applied to any declaration to
/// specify that such types are isolated to that global actor type. When using
/// such a declaration from another actor (or from nonisolated code),
/// synchronization is performed through the \c shared actor instance to ensure
/// mutually-exclusive access to the declaration.
@available(SwiftStdlib 5.5, *)
public protocol GlobalActor {
/// The type of the shared actor instance that will be used to provide
/// mutually-exclusive access to declarations annotated with the given global
/// actor type.
associatedtype ActorType: Actor

/// The shared actor instance that will be used to provide mutually-exclusive
/// access to declarations annotated with the given global actor type.
///
/// The value of this property must always evaluate to the same actor
/// instance.
static var shared: ActorType { get }

/// The shared executor instance that will be used to provide
/// mutually-exclusive access for the global actor.
///
/// The value of this property must be equivalent to `shared.unownedExecutor`.
static var sharedUnownedExecutor: UnownedSerialExecutor { get }
}

@available(SwiftStdlib 5.5, *)
extension GlobalActor {
public static var sharedUnownedExecutor: UnownedSerialExecutor {
shared.unownedExecutor
}
}

50 changes: 50 additions & 0 deletions stdlib/public/Concurrency/MainActor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Swift

/// A singleton actor whose executor is equivalent to the main
/// dispatch queue.
@available(SwiftStdlib 5.5, *)
@globalActor public final actor MainActor: SerialExecutor, GlobalActor {
public static let shared = MainActor()

@inlinable
public nonisolated var unownedExecutor: UnownedSerialExecutor {
return asUnownedSerialExecutor()
}

@inlinable
public nonisolated func asUnownedSerialExecutor() -> UnownedSerialExecutor {
return UnownedSerialExecutor(ordinary: self)
}

@inlinable
public nonisolated func enqueue(_ job: UnownedJob) {
_enqueueOnMain(job)
}
}

@available(SwiftStdlib 5.5, *)
extension MainActor {
/// Execute the given body closure on the main actor.
public static func run<T>(
resultType: T.Type = T.self,
body: @MainActor @Sendable () throws -> T
) async rethrows -> T {
@MainActor func runOnMain(body: @MainActor @Sendable () throws -> T) async rethrows -> T {
return try body()
}

return try await runOnMain(body: body)
}
}
24 changes: 13 additions & 11 deletions test/attr/global_actor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,37 @@ struct GenericGlobalActor<T> {

// 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#>}}
open class GA2 { // expected-error{{type 'GA2' does not conform to protocol 'GlobalActor'}}
}

@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 }}
struct GA3 { // expected-error{{type 'GA3' does not conform to protocol 'GlobalActor'}}
let shared = SomeActor()
}

@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=}}
struct GA4 {
private static let shared = SomeActor() // expected-error{{property 'shared' must be as accessible as its enclosing type because it matches a requirement in protocol 'GlobalActor'}}
// expected-note@-1{{mark the static property as 'internal' to satisfy the requirement}}
}

@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}}
open class GA5 {
static let shared = SomeActor() // expected-error{{property 'shared' must be declared public because it matches a requirement in public protocol 'GlobalActor'}}
// expected-note@-1{{mark the static property as 'public' to satisfy the requirement}}
}

@globalActor
struct GA6<T> { // expected-error{{global actor 'GA6' requires a static property 'shared' that produces an actor instance}}
struct GA6<T> { // expected-error{{type 'GA6<T>' does not conform to protocol 'GlobalActor'}}
}

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

@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}}
class GA7 { // expected-error{{type 'GA7' does not conform to protocol 'GlobalActor'}}
static let shared = 5 // expected-note{{candidate would match and infer 'ActorType' = 'Int' if 'Int' conformed to 'Actor'}}
}

// -----------------------------------------------------------------------
Expand Down