Skip to content

Ban classes from extending Actor and DistributedActor protocol explicitly #38050

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 2 commits into from
Jun 24, 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
1 change: 1 addition & 0 deletions docs/Diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Clang also has a kind of diagnostic called a "remark", which represents informat
- "...to silence this warning"
- "...here" (for a purely locational note)

- If possible, it is best to include the name of the type or function that has the error, e.g. "non-actor type 'Nope' cannot ..." is better than "non-actor type cannot ...". It helps developers relate the error message to the specific type the error is about, even if the error would highlight the appropriate line / function in other ways.

### Locations and Highlights ###

Expand Down
10 changes: 9 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4349,7 +4349,15 @@ ERROR(async_objc_dynamic_self,none,
"asynchronous method returning 'Self' cannot be '@objc'", ())

ERROR(actor_inheritance,none,
"actor types do not support inheritance", ())
"%select{actor|distributed actor}0 types do not support inheritance",
(bool))

ERROR(actor_protocol_illegal_inheritance,none,
"non-actor type %0 cannot conform to the 'Actor' protocol",
(DeclName))
ERROR(distributed_actor_protocol_illegal_inheritance,none,
"non-distributed actor type %0 cannot conform to the 'DistributedActor' protocol",
(DeclName))

// FIXME: This diagnostic was temporarily downgraded from an error because
// it spuriously triggers when building the Foundation module from its textual
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ class IsDefaultActorRequest :
bool isCached() const { return true; }
};

/// Determine whether the given class is an distributed actor.
/// Determine whether the given class is a distributed actor.
class IsDistributedActorRequest :
public SimpleRequest<IsDistributedActorRequest,
bool(NominalTypeDecl *),
Expand Down
3 changes: 2 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5167,7 +5167,8 @@ void ProtocolDecl::computeKnownProtocolKind() const {
if (module != module->getASTContext().getStdlibModule() &&
!module->getName().is("Foundation") &&
!module->getName().is("_Differentiation") &&
!module->getName().is("_Concurrency")) {
!module->getName().is("_Concurrency") &&
!module->getName().is("_Distributed")) {
const_cast<ProtocolDecl *>(this)->Bits.ProtocolDecl.KnownProtocol = 1;
return;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1250,10 +1250,10 @@ void NominalTypeDecl::prepareConformanceTable() const {

// Actor classes conform to the actor protocol.
if (auto classDecl = dyn_cast<ClassDecl>(mutableThis)) {
if (classDecl->isActor())
addSynthesized(KnownProtocolKind::Actor);
if (classDecl->isDistributedActor())
addSynthesized(KnownProtocolKind::DistributedActor);
else if (classDecl->isActor())
addSynthesized(KnownProtocolKind::Actor);
}

// Global actors conform to the GlobalActor protocol.
Expand Down
6 changes: 4 additions & 2 deletions lib/Sema/TypeCheckDeclPrimary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2396,9 +2396,11 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
if (auto superclass = CD->getSuperclassDecl()) {
// Actors cannot have superclasses, nor can they be superclasses.
if (CD->isActor() && !superclass->isNSObject())
CD->diagnose(diag::actor_inheritance);
CD->diagnose(diag::actor_inheritance,
/*distributed=*/CD->isDistributedActor());
else if (superclass->isActor())
CD->diagnose(diag::actor_inheritance);
CD->diagnose(diag::actor_inheritance,
/*distributed=*/CD->isDistributedActor());
}

// Force lowering of stored properties.
Expand Down
25 changes: 25 additions & 0 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5851,6 +5851,31 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
}
} else if (proto->isSpecificProtocol(KnownProtocolKind::Sendable)) {
SendableConformance = conformance;
} else if (proto->isSpecificProtocol(KnownProtocolKind::DistributedActor)) {
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
if (!classDecl->isDistributedActor()) {
if (classDecl->isActor()) {
dc->getSelfNominalTypeDecl()
->diagnose(diag::distributed_actor_protocol_illegal_inheritance,
dc->getSelfNominalTypeDecl()->getName())
.fixItInsert(classDecl->getStartLoc(), "distributed ");
} else {
dc->getSelfNominalTypeDecl()
->diagnose(diag::actor_protocol_illegal_inheritance,
dc->getSelfNominalTypeDecl()->getName())
.fixItReplace(nominal->getStartLoc(), "distributed actor");
}
}
}
} else if (proto->isSpecificProtocol(KnownProtocolKind::Actor)) {
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
if (!classDecl->isExplicitActor()) {
dc->getSelfNominalTypeDecl()
->diagnose(diag::actor_protocol_illegal_inheritance,
dc->getSelfNominalTypeDecl()->getName())
.fixItReplace(nominal->getStartLoc(), "actor");
}
}
} else if (proto->isSpecificProtocol(
KnownProtocolKind::UnsafeSendable)) {
unsafeSendableConformance = conformance;
Expand Down
47 changes: 47 additions & 0 deletions stdlib/public/Distributed/ActorTransport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2020-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
import _Concurrency

@available(SwiftStdlib 5.5, *)
public protocol ActorTransport: Sendable {
/// Resolve a local or remote actor address to a real actor instance, or throw if unable to.
/// The returned value is either a local actor or proxy to a remote actor.
func resolve<Act>(address: ActorAddress, as actorType: Act.Type)
throws -> ActorResolved<Act> where Act: DistributedActor

/// Create an `ActorAddress` for the passed actor type.
///
/// This function is invoked by an distributed actor during its initialization,
/// and the returned address value is stored along with it for the time of its
/// lifetime.
///
/// The address MUST uniquely identify the actor, and allow resolving it.
/// E.g. if an actor is created under address `addr1` then immediately invoking
/// `transport.resolve(address: addr1, as: Greeter.self)` MUST return a reference
/// to the same actor.
func assignAddress<Act>(_ actorType: Act.Type) -> ActorAddress
where Act: DistributedActor

func actorReady<Act>(_ actor: Act) where Act: DistributedActor

/// Called during actor deinit/destroy.
func resignAddress(_ address: ActorAddress)

}

@available(SwiftStdlib 5.5, *)
public enum ActorResolved<Act: DistributedActor> {
case resolved(Act)
case makeProxy
}
1 change: 1 addition & 0 deletions stdlib/public/Distributed/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set(swift_distributed_link_libraries

add_swift_target_library(swift_Distributed ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB
AssertDistributed.swift
ActorTransport.swift
DistributedActor.swift

SWIFT_MODULE_DEPENDS_LINUX Glibc
Expand Down
55 changes: 12 additions & 43 deletions stdlib/public/Distributed/DistributedActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
import Swift
import _Concurrency

// ==== Any Actor -------------------------------------------------------------

/// Shared "base" protocol for both (local) `Actor` and (potentially remote)
/// `DistributedActor`.
///
/// FIXME: !!! We'd need Actor to also conform to this, but don't want to add that conformance in _Concurrency yet.
@_marker
public protocol AnyActor: AnyObject {}

// ==== Distributed Actor -----------------------------------------------------

/// Common protocol to which all distributed actors conform implicitly.
///
/// It is not possible to conform to this protocol manually explicitly.
Expand All @@ -24,7 +35,7 @@ import _Concurrency
/// which involves enqueuing new partial tasks to be executed at some
/// point.
@available(SwiftStdlib 5.5, *)
public protocol DistributedActor: Actor, Codable {
public protocol DistributedActor: AnyActor, Codable {

/// Creates new (local) distributed actor instance, bound to the passed transport.
///
Expand Down Expand Up @@ -91,48 +102,6 @@ extension DistributedActor {
try container.encode(self.actorAddress)
}
}
/******************************************************************************/
/***************************** Actor Transport ********************************/
/******************************************************************************/

@available(SwiftStdlib 5.5, *)
public protocol ActorTransport: Sendable {
/// Resolve a local or remote actor address to a real actor instance, or throw if unable to.
/// The returned value is either a local actor or proxy to a remote actor.
func resolve<Act>(address: ActorAddress, as actorType: Act.Type)
throws -> ActorResolved<Act> where Act: DistributedActor

/// Create an `ActorAddress` for the passed actor type.
///
/// This function is invoked by an distributed actor during its initialization,
/// and the returned address value is stored along with it for the time of its
/// lifetime.
///
/// The address MUST uniquely identify the actor, and allow resolving it.
/// E.g. if an actor is created under address `addr1` then immediately invoking
/// `transport.resolve(address: addr1, as: Greeter.self)` MUST return a reference
/// to the same actor.
func assignAddress<Act>(
_ actorType: Act.Type
) -> ActorAddress
where Act: DistributedActor

func actorReady<Act>(
_ actor: Act
) where Act: DistributedActor

/// Called during actor deinit/destroy.
func resignAddress(
_ address: ActorAddress
)

}

@available(SwiftStdlib 5.5, *)
public enum ActorResolved<Act: DistributedActor> {
case resolved(Act)
case makeProxy
}

/******************************************************************************/
/***************************** Actor Address **********************************/
Expand Down
14 changes: 14 additions & 0 deletions test/Concurrency/actor_isolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,20 @@ func outsideSomeClassWithInits() { // expected-note 3 {{add '@MainActor' to make
// ----------------------------------------------------------------------
// Actor protocols.
// ----------------------------------------------------------------------

@available(SwiftStdlib 5.5, *)
actor A: Actor { // ok
}

@available(SwiftStdlib 5.5, *)
class C: Actor, UnsafeSendable {
// expected-error@-1{{non-actor type 'C' cannot conform to the 'Actor' protocol}}
// expected-warning@-2{{'UnsafeSendable' is deprecated: Use @unchecked Sendable instead}}
nonisolated var unownedExecutor: UnownedSerialExecutor {
fatalError()
}
}

@available(SwiftStdlib 5.5, *)
protocol P: Actor {
func f()
Expand Down
83 changes: 83 additions & 0 deletions test/Distributed/actor_protocols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// RUN: %target-typecheck-verify-swift -enable-experimental-distributed
// REQUIRES: concurrency
// REQUIRES: distributed

import _Distributed

// ==== -----------------------------------------------------------------------

@available(SwiftStdlib 5.5, *)
actor A: Actor {} // ok

@available(SwiftStdlib 5.5, *)
class C: Actor, UnsafeSendable {
// expected-error@-1{{non-actor type 'C' cannot conform to the 'Actor' protocol}} {{1-6=actor}}
// expected-warning@-2{{'UnsafeSendable' is deprecated: Use @unchecked Sendable instead}}
nonisolated var unownedExecutor: UnownedSerialExecutor {
fatalError()
}
}

@available(SwiftStdlib 5.5, *)
struct S: Actor {
// expected-error@-1{{non-class type 'S' cannot conform to class protocol 'Actor'}}
nonisolated var unownedExecutor: UnownedSerialExecutor {
fatalError()
}
}

@available(SwiftStdlib 5.5, *)
struct E: Actor {
// expected-error@-1{{non-class type 'E' cannot conform to class protocol 'Actor'}}
nonisolated var unownedExecutor: UnownedSerialExecutor {
fatalError()
}
}

// ==== -----------------------------------------------------------------------

@available(SwiftStdlib 5.5, *)
distributed actor DA: DistributedActor {} // ok

@available(SwiftStdlib 5.5, *)
actor A2: DistributedActor {
// expected-error@-1{{non-distributed actor type 'A2' cannot conform to the 'DistributedActor' protocol}} {{1-1=distributed }}
nonisolated var actorAddress: ActorAddress {
fatalError()
}
nonisolated var actorTransport: ActorTransport {
fatalError()
}

required init(transport: ActorTransport) {
fatalError()
}
required init(resolve address: ActorAddress, using transport: ActorTransport) throws {
fatalError()
}
}

@available(SwiftStdlib 5.5, *)
class C2: DistributedActor {
// expected-error@-1{{non-actor type 'C2' cannot conform to the 'Actor' protocol}}
nonisolated var actorAddress: ActorAddress {
fatalError()
}
nonisolated var actorTransport: ActorTransport {
fatalError()
}

required init(transport: ActorTransport) {
fatalError()
}
required init(resolve address: ActorAddress, using transport: ActorTransport) throws {
fatalError()
}
}

@available(SwiftStdlib 5.5, *)
struct S2: DistributedActor {
// expected-error@-1{{non-class type 'S2' cannot conform to class protocol 'DistributedActor'}}
// expected-error@-2{{non-class type 'S2' cannot conform to class protocol 'AnyActor'}}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %target-typecheck-verify-swift -enable-experimental-distributed
// REQUIRES: concurrency
// REQUIRES: distributed

import _Distributed

@available(SwiftStdlib 5.5, *)
extension Actor {
func f() -> String { "Life is Study!" }
}

@available(SwiftStdlib 5.5, *)
func g<A: Actor>(a: A) async { // expected-note{{where 'A' = 'MA'}}
print(await a.f())
}

@available(SwiftStdlib 5.5, *)
distributed actor MA {
}

@available(SwiftStdlib 5.5, *)
func h(ma: MA) async {
// this would have been a bug, a non distributed function might have been called here,
// so we must not allow for it, because if the actor was remote calling a non-distributed func
// would result in a hard crash (as there is no local actor to safely call the function on).
await g(a: ma) // expected-error{{global function 'g(a:)' requires that 'MA' conform to 'Actor'}}
}
6 changes: 4 additions & 2 deletions test/decl/class/actor/basic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

actor MyActor { }

class MyActorSubclass1: MyActor { } // expected-error{{actor types do not support inheritance}}
// expected-error@-1{{non-final class 'MyActorSubclass1' cannot conform to `Sendable`; use `UnsafeSendable`}}
class MyActorSubclass1: MyActor { }
// expected-error@-1{{actor types do not support inheritance}}
// expected-error@-2{{type 'MyActorSubclass1' cannot conform to the 'Actor' protocol}}
// expected-error@-3{{non-final class 'MyActorSubclass1' cannot conform to `Sendable`; use `UnsafeSendable`}}

actor MyActorSubclass2: MyActor { } // expected-error{{actor types do not support inheritance}}

Expand Down
Loading