[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. /// Find the concrete invocation decoder associated with the given actor.
NominalTypeDecl *getDistributedActorInvocationDecoder(NominalTypeDecl *); 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 /// Find `decodeNextArgument<T>(type: T.Type) -> T` method associated with
/// invocation decoder of the given distributed actor. /// invocation decoder of the given distributed actor.
FuncDecl *getDistributedActorArgumentDecodingMethod(NominalTypeDecl *); FuncDecl *getDistributedActorArgumentDecodingMethod(NominalTypeDecl *);

View File

@@ -1054,6 +1054,24 @@ public:
bool isCached() const { return true; } 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 /// Retrieve the implicit conformance for the given distributed actor type to
/// the Codable protocol protocol. /// the Codable protocol protocol.
/// ///

View File

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

View File

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

View File

@@ -60,6 +60,23 @@
using namespace swift; 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 *********************/ /************** Distributed Actor System Associated Types *********************/
/******************************************************************************/ /******************************************************************************/
@@ -210,9 +227,11 @@ Type swift::getDistributedActorSystemInvocationDecoderType(NominalTypeDecl *syst
Type swift::getDistributedSerializationRequirementType( Type swift::getDistributedSerializationRequirementType(
NominalTypeDecl *nominal, ProtocolDecl *protocol) { NominalTypeDecl *nominal, ProtocolDecl *protocol) {
assert(nominal); assert(nominal);
assert(protocol);
auto &ctx = nominal->getASTContext(); auto &ctx = nominal->getASTContext();
if (!protocol)
return Type();
// Dig out the serialization requirement type. // Dig out the serialization requirement type.
auto module = nominal->getParentModule(); auto module = nominal->getParentModule();
Type selfType = nominal->getSelfInterfaceType(); Type selfType = nominal->getSelfInterfaceType();
@@ -220,7 +239,8 @@ Type swift::getDistributedSerializationRequirementType(
if (conformance.isInvalid()) if (conformance.isInvalid())
return Type(); return Type();
return conformance.getTypeWitnessByName(selfType, ctx.Id_SerializationRequirement); return conformance.getTypeWitnessByName(selfType,
ctx.Id_SerializationRequirement);
} }
AbstractFunctionDecl * AbstractFunctionDecl *

View File

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

View File

@@ -1043,3 +1043,29 @@ NormalProtocolConformance *GetDistributedActorImplicitCodableRequest::evaluate(
return addDistributedActorCodableConformance(classDecl, return addDistributedActorCodableConformance(classDecl,
C.getProtocol(protoKind)); 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 returnType: Any.Type? = nil
var errorType: Any.Type? = nil var errorType: Any.Type? = nil
public init() {}
public mutating func recordGenericSubstitution<T>(_ type: T.Type) throws { public mutating func recordGenericSubstitution<T>(_ type: T.Type) throws {
print(" > encode generic sub: \(String(reflecting: type))") print(" > encode generic sub: \(String(reflecting: type))")
genericSubs.append(type) genericSubs.append(type)

View File

@@ -17,14 +17,11 @@
import Distributed import Distributed
@available(SwiftStdlib 6.0, *) @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) { distributed func hi(name: String) {
print("Hi, \(name)!") print("Hi, \(name)!")
} }
nonisolated var description: Swift.String {
"Worker(\(id))"
}
} }
// ==== Execute ---------------------------------------------------------------- // ==== 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: %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 -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 // RUN: %target-swift-frontend -typecheck -verify -disable-availability-checking -I %t 2>&1 %s
// X: %target-swift-frontend -typecheck -verify -disable-availability-checking -I %t 2>&1 %s
// REQUIRES: concurrency // REQUIRES: concurrency
// REQUIRES: distributed // REQUIRES: distributed
@@ -15,7 +14,7 @@ protocol Greeting: DistributedActor {
} }
extension Greeting { 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 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 { nonisolated func greetAliceALot() async throws {
// try await greetDistributed(name: "Alice") // okay, via Codable try await self.greetDistributed(name: "Alice") // okay, via Codable
// let rawGreeting = try await greeting() // okay, via Self's serialization requirement 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}} _ = 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@-1{{distributed actor 'DA' does not declare ActorSystem it can be used with}}
// expected-error@-2 {{type 'DA' does not conform to protocol 'DistributedActor'}} // expected-error@-2 {{type 'DA' does not conform to protocol 'DistributedActor'}}
// Since synthesis would have failed due to the missing ActorSystem: // expected-note@-4{{you can provide a module-wide default actor system by declaring:}}
// 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:}}
// Note to add the typealias is diagnosed on the protocol: // 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? // _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}} 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-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-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 typealias ActorSystem = DoesNotExistDataSystem
// expected-error@-1{{cannot find type 'DoesNotExistDataSystem' in scope}} // expected-error@-1{{cannot find type 'DoesNotExistDataSystem' in scope}}
typealias SerializationRequirement = any Codable 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@-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-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-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) { distributed func tell(_ text: String, by: Fish) {
// What would the fish say, if it could talk? // 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@-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@-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 // Entire conformance is doomed, so we didn't proceed to checking the functions; that's fine
distributed func testAT() async throws -> NotCodable { .init() } distributed func testAT() async throws -> NotCodable { .init() }