[Distributed] Only synthesize Codable for DA where the ID is Codable (#72081)

This commit is contained in:
Konrad `ktoso` Malawski
2024-03-08 15:40:00 +09:00
committed by GitHub
parent 8bfe9d0395
commit b7ff16baf7
14 changed files with 213 additions and 27 deletions

View File

@@ -37,6 +37,17 @@ Type getAssociatedTypeOfDistributedSystemOfActor(DeclContext *actorOrExtension,
/// Find the concrete invocation decoder associated with the given actor.
NominalTypeDecl *getDistributedActorInvocationDecoder(NominalTypeDecl *);
/// Determine if this distributed actor can synthesize a `Codable` conformance.
/// This is based on the actor's `ID` being `Codable`.
///
/// It is possible for the `ID` to be `Codable` but the
/// `SerializationRequirement` used by the actor (and its actor system to not
/// be `Codable`). In such situation the conformance is synthesized, however
/// the user may need to provide an explicit conformance to the
/// `SerializationRequirement` if they wanted to pass the actor to distributed
/// methods.
bool canSynthesizeDistributedActorCodableConformance(NominalTypeDecl *actor);
/// Find `decodeNextArgument<T>(type: T.Type) -> T` method associated with
/// invocation decoder of the given distributed actor.
FuncDecl *getDistributedActorArgumentDecodingMethod(NominalTypeDecl *);

View File

@@ -1054,6 +1054,24 @@ public:
bool isCached() const { return true; }
};
/// Determine whether the given class is a distributed actor.
class CanSynthesizeDistributedActorCodableConformanceRequest :
public SimpleRequest<CanSynthesizeDistributedActorCodableConformanceRequest,
bool(NominalTypeDecl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;
private:
friend SimpleRequest;
bool evaluate(Evaluator &evaluator, NominalTypeDecl *nominal) const;
public:
// Caching
bool isCached() const { return true; }
};
/// Retrieve the implicit conformance for the given distributed actor type to
/// the Codable protocol protocol.
///

View File

@@ -120,6 +120,9 @@ SWIFT_REQUEST(TypeChecker, IsDistributedActorRequest, bool(NominalTypeDecl *),
SWIFT_REQUEST(TypeChecker, GetDistributedActorImplicitCodableRequest,
NormalProtocolConformance *(NominalTypeDecl *, KnownProtocolKind),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, CanSynthesizeDistributedActorCodableConformanceRequest,
bool (NominalTypeDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, GetDistributedActorSystemRemoteCallFunctionRequest,
AbstractFunctionDecl *(NominalTypeDecl *, bool),
Cached, NoLocationInfo)

View File

@@ -26,6 +26,7 @@
#include "swift/AST/Module.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Builtins.h"
#include "swift/AST/DistributedDecl.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/GenericEnvironment.h"
@@ -622,7 +623,8 @@ LookupConformanceInModuleRequest::evaluate(
}
} else if (protocol->isSpecificProtocol(KnownProtocolKind::Encodable) ||
protocol->isSpecificProtocol(KnownProtocolKind::Decodable)) {
if (nominal->isDistributedActor()) {
// if (nominal->isDistributedActor()) {
if (canSynthesizeDistributedActorCodableConformance(nominal)) {
auto protoKind =
protocol->isSpecificProtocol(KnownProtocolKind::Encodable)
? KnownProtocolKind::Encodable

View File

@@ -60,6 +60,23 @@
using namespace swift;
/******************************************************************************/
/************* Implicit Distributed Actor Codable Conformance *****************/
/******************************************************************************/
bool swift::canSynthesizeDistributedActorCodableConformance(NominalTypeDecl *actor) {
auto &C = actor->getASTContext();
if (!actor->isDistributedActor())
return false;
return evaluateOrDefault(
C.evaluator,
CanSynthesizeDistributedActorCodableConformanceRequest{actor},
false);
}
/******************************************************************************/
/************** Distributed Actor System Associated Types *********************/
/******************************************************************************/
@@ -210,9 +227,11 @@ Type swift::getDistributedActorSystemInvocationDecoderType(NominalTypeDecl *syst
Type swift::getDistributedSerializationRequirementType(
NominalTypeDecl *nominal, ProtocolDecl *protocol) {
assert(nominal);
assert(protocol);
auto &ctx = nominal->getASTContext();
if (!protocol)
return Type();
// Dig out the serialization requirement type.
auto module = nominal->getParentModule();
Type selfType = nominal->getSelfInterfaceType();
@@ -220,7 +239,8 @@ Type swift::getDistributedSerializationRequirementType(
if (conformance.isInvalid())
return Type();
return conformance.getTypeWitnessByName(selfType, ctx.Id_SerializationRequirement);
return conformance.getTypeWitnessByName(selfType,
ctx.Id_SerializationRequirement);
}
AbstractFunctionDecl *

View File

@@ -1284,7 +1284,7 @@ static SmallVector<ProtocolConformance *, 2> findSynthesizedConformances(
}
/// Distributed actors can synthesize Encodable/Decodable, so look for those
if (nominal->isDistributedActor()) {
if (canSynthesizeDistributedActorCodableConformance(nominal)) {
trySynthesize(KnownProtocolKind::Encodable);
trySynthesize(KnownProtocolKind::Decodable);
}

View File

@@ -1043,3 +1043,29 @@ NormalProtocolConformance *GetDistributedActorImplicitCodableRequest::evaluate(
return addDistributedActorCodableConformance(classDecl,
C.getProtocol(protoKind));
}
bool CanSynthesizeDistributedActorCodableConformanceRequest::evaluate(
Evaluator &evaluator, NominalTypeDecl *actor) const {
if (actor && !isa<ClassDecl>(actor))
return false;
if (!actor->isDistributedActor())
return false;
auto systemTy = getConcreteReplacementForProtocolActorSystemType(actor);
if (!systemTy)
return false;
if (!systemTy->getAnyNominal())
return false;
auto idTy = getDistributedActorSystemActorIDType(systemTy->getAnyNominal());
if (!idTy)
return false;
return TypeChecker::conformsToKnownProtocol(
idTy, KnownProtocolKind::Decodable, actor->getParentModule()) &&
TypeChecker::conformsToKnownProtocol(
idTy, KnownProtocolKind::Encodable, actor->getParentModule());
}

View File

@@ -385,6 +385,8 @@ public struct FakeInvocationEncoder : DistributedTargetInvocationEncoder {
var returnType: Any.Type? = nil
var errorType: Any.Type? = nil
public init() {}
public mutating func recordGenericSubstitution<T>(_ type: T.Type) throws {
print(" > encode generic sub: \(String(reflecting: type))")
genericSubs.append(type)

View File

@@ -17,14 +17,11 @@
import Distributed
@available(SwiftStdlib 6.0, *)
distributed actor Worker<ActorSystem> where ActorSystem: DistributedActorSystem<any Codable>, ActorSystem.ActorID: Codable {
//distributed actor Worker<ActorSystem> where ActorSystem: DistributedActorSystem<any Codable>, ActorSystem.ActorID: Codable {
distributed actor Worker<ActorSystem> where ActorSystem: DistributedActorSystem<any Codable> {
distributed func hi(name: String) {
print("Hi, \(name)!")
}
nonisolated var description: Swift.String {
"Worker(\(id))"
}
}
// ==== Execute ----------------------------------------------------------------

View File

@@ -0,0 +1,116 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/Inputs/FakeDistributedActorSystems.swift
// RUN: %target-swift-frontend -typecheck -verify -disable-availability-checking -I %t 2>&1 %s
// REQUIRES: concurrency
// REQUIRES: distributed
import Distributed
import FakeDistributedActorSystems
distributed actor YesVeryMuchSo {
typealias ActorSystem = FakeRoundtripActorSystem
}
func test_YesVeryMuchSo(_ actor: YesVeryMuchSo) {
let _: any Codable = actor // implicit conformance, ID was Codable
// unrelated protocol
let _: any CustomSerializationProtocol = actor // expected-error{{value of type 'YesVeryMuchSo' does not conform to specified type 'CustomSerializationProtocol'}}
}
// ==== ------------------------------------------------------------------------
distributed actor SerReqNotCodable {
typealias ActorSystem = FakeCustomSerializationRoundtripActorSystem
}
func test_NotAtAll_NoImplicit(_ actor: SerReqNotCodable) {
let _: any Codable = actor // OK, the ID was Codable, even though SerializationRequirement was something else
// no implicit conformance
let _: any CustomSerializationProtocol = actor // expected-error{{value of type 'SerReqNotCodable' does not conform to specified type 'CustomSerializationProtocol'}}
}
// ==== ------------------------------------------------------------------------
distributed actor NotAtAll_NothingCodable {
typealias ActorSystem = FakeIdIsNotCodableActorSystem
}
func test_NotAtAll_NoImplicit(_ actor: NotAtAll_NothingCodable) {
let _: any Codable = actor // expected-error{{value of type 'NotAtAll_NothingCodable' does not conform to specified type 'Decodable'}}
// no implicit conformance
let _: any CustomSerializationProtocol = actor // expected-error{{value of type 'NotAtAll_NothingCodable' does not conform to specified type 'CustomSerializationProtocol'}}
}
public struct NotCodableID: Sendable, Hashable {}
@available(SwiftStdlib 5.7, *)
public struct FakeIdIsNotCodableActorSystem: DistributedActorSystem, CustomStringConvertible {
public typealias ActorID = NotCodableID
public typealias InvocationDecoder = FakeInvocationDecoder
public typealias InvocationEncoder = FakeInvocationEncoder
public typealias SerializationRequirement = Codable
public typealias ResultHandler = FakeRoundtripResultHandler
// just so that the struct does not become "trivial"
let someValue: String = ""
let someValue2: String = ""
let someValue3: String = ""
let someValue4: String = ""
public init() {
}
public func resolve<Act>(id: ActorID, as actorType: Act.Type) throws -> Act?
where Act: DistributedActor,
Act.ID == ActorID {
nil
}
public func assignID<Act>(_ actorType: Act.Type) -> ActorID
where Act: DistributedActor,
Act.ID == ActorID {
.init()
}
public func actorReady<Act>(_ actor: Act)
where Act: DistributedActor,
Act.ID == ActorID {
}
public func resignID(_ id: ActorID) {
}
public func makeInvocationEncoder() -> InvocationEncoder {
.init()
}
public func remoteCall<Act, Err, Res>(
on actor: Act,
target: RemoteCallTarget,
invocation invocationEncoder: inout InvocationEncoder,
throwing: Err.Type,
returning: Res.Type
) async throws -> Res
where Act: DistributedActor,
Act.ID == ActorID,
Err: Error,
Res: SerializationRequirement {
throw ExecuteDistributedTargetError(message: "\(#function) not implemented.")
}
public func remoteCallVoid<Act, Err>(
on actor: Act,
target: RemoteCallTarget,
invocation invocationEncoder: inout InvocationEncoder,
throwing: Err.Type
) async throws
where Act: DistributedActor,
Act.ID == ActorID,
Err: Error {
throw ExecuteDistributedTargetError(message: "\(#function) not implemented.")
}
public nonisolated var description: Swift.String {
"\(Self.self)()"
}
}

View File

@@ -1,7 +1,6 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/Inputs/FakeDistributedActorSystems.swift
// RUN: %target-swift-frontend-emit-module -I %t -emit-module-path %t/distributed_actor_protocol_isolation.swiftmodule -module-name distributed_actor_protocol_isolation -disable-availability-checking %s
// X: %target-swift-frontend -typecheck -verify -disable-availability-checking -I %t 2>&1 %s
// RUN: %target-swift-frontend -typecheck -verify -disable-availability-checking -I %t 2>&1 %s
// REQUIRES: concurrency
// REQUIRES: distributed
@@ -15,7 +14,7 @@ protocol Greeting: DistributedActor {
}
extension Greeting {
func greetLocal(name: String) {
func greetLocal(name: String) { // expected-note{{distributed actor-isolated instance method 'greetLocal(name:)' declared here}}
print("\(greeting()), \(name)!") // okay, we're on the actor
}
}
@@ -28,10 +27,12 @@ extension Greeting where SerializationRequirement == Codable {
}
}
extension Greeting {
extension Greeting where Self.SerializationRequirement == Codable {
nonisolated func greetAliceALot() async throws {
// try await greetDistributed(name: "Alice") // okay, via Codable
// let rawGreeting = try await greeting() // okay, via Self's serialization requirement
// greetLocal(name: "Alice") // expected-error{{only 'distributed' instance methods can be called on a potentially remote distributed actor}}
try await self.greetDistributed(name: "Alice") // okay, via Codable
let rawGreeting = try await greeting() // okay, via Self's serialization requirement
_ = rawGreeting
greetLocal(name: "Alice") // expected-error{{only 'distributed' instance methods can be called on a potentially remote distributed actor}}
}
}

View File

@@ -10,11 +10,7 @@ distributed actor DA {
// expected-error@-1{{distributed actor 'DA' does not declare ActorSystem it can be used with}}
// expected-error@-2 {{type 'DA' does not conform to protocol 'DistributedActor'}}
// Since synthesis would have failed due to the missing ActorSystem:
// expected-error@-5{{type 'DA' does not conform to protocol 'Encodable'}}
// expected-error@-6{{type 'DA' does not conform to protocol 'Decodable'}}
// expected-note@-8{{you can provide a module-wide default actor system by declaring:}}
// expected-note@-4{{you can provide a module-wide default actor system by declaring:}}
// Note to add the typealias is diagnosed on the protocol:
// _Distributed.DistributedActor:3:20: note: diagnostic produced elsewhere: protocol requires nested type 'ActorSystem'; do you want to add it?
@@ -27,8 +23,6 @@ distributed actor DA {
distributed actor Server { // expected-error 2 {{distributed actor 'Server' does not declare ActorSystem it can be used with}}
// expected-error@-1 {{type 'Server' does not conform to protocol 'DistributedActor'}}
// expected-note@-2{{you can provide a module-wide default actor system by declaring:}}
// expected-error@-3{{type 'Server' does not conform to protocol 'Encodable'}}
// expected-error@-4{{type 'Server' does not conform to protocol 'Decodable'}}
typealias ActorSystem = DoesNotExistDataSystem
// expected-error@-1{{cannot find type 'DoesNotExistDataSystem' in scope}}
typealias SerializationRequirement = any Codable

View File

@@ -12,8 +12,6 @@ distributed actor Fish {
// expected-error@-2{{distributed actor 'Fish' does not declare ActorSystem it can be used with}}
// expected-error@-3{{type 'Fish' does not conform to protocol 'DistributedActor'}}
// expected-note@-4{{you can provide a module-wide default actor system by declaring:\ntypealias DefaultDistributedActorSystem = <#ConcreteActorSystem#>}}
// expected-error@-5{{type 'Fish' does not conform to protocol 'Encodable'}}
// expected-error@-6{{type 'Fish' does not conform to protocol 'Decodable'}}
distributed func tell(_ text: String, by: Fish) {
// What would the fish say, if it could talk?

View File

@@ -53,8 +53,6 @@ distributed actor ProtocolWithChecksSeqReqDA_MissingSystem: ProtocolWithChecksSe
// expected-error@-4{{distributed actor 'ProtocolWithChecksSeqReqDA_MissingSystem' does not declare ActorSystem it can be used with}}
//
// expected-error@-6{{type 'ProtocolWithChecksSeqReqDA_MissingSystem' does not conform to protocol 'DistributedActor'}}
// expected-error@-7{{type 'ProtocolWithChecksSeqReqDA_MissingSystem' does not conform to protocol 'Encodable'}}
// expected-error@-8{{type 'ProtocolWithChecksSeqReqDA_MissingSystem' does not conform to protocol 'Decodable'}}
// Entire conformance is doomed, so we didn't proceed to checking the functions; that's fine
distributed func testAT() async throws -> NotCodable { .init() }