diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index ec7d15e721aab..7c608c6768f69 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -2943,6 +2943,29 @@ AnyFunctionType::Param swift::computeSelfParam(AbstractFunctionDecl *AFD, // inout self. if (!containerTy->hasReferenceSemantics()) selfAccess = SelfAccessKind::Mutating; + + // FIXME(distributed): pending swift-evolution, allow `self =` in class + // inits in general. + // See also: https://github.com/apple/swift/pull/19151 general impl + if (Ctx.LangOpts.EnableExperimentalDistributed) { + auto ext = dyn_cast(AFD->getDeclContext()); + auto distProto = + Ctx.getProtocol(KnownProtocolKind::DistributedActor); + if (ext && ext->getExtendedNominal() && + ext->getExtendedNominal()->getInterfaceType() + ->isEqual(distProto->getInterfaceType())) { + auto name = CD->getName(); + auto params = name.getArgumentNames(); + if (params.size() == 1 && params[0] == Ctx.Id_from) { + // FIXME(distributed): this is a workaround to allow init(from:) to + // be implemented in AST by allowing the self to be mutable in the + // decoding initializer. This should become a general Swift + // feature, allowing this in all classes: + // https://forums.swift.org/t/allow-self-x-in-class-convenience-initializers/15924 + selfAccess = SelfAccessKind::Mutating; + } + } + } } else { // allocating constructors have metatype 'self'. isStatic = true; diff --git a/stdlib/public/Distributed/ActorTransport.swift b/stdlib/public/Distributed/ActorTransport.swift index 66d64d8689b92..a3dbb3e129669 100644 --- a/stdlib/public/Distributed/ActorTransport.swift +++ b/stdlib/public/Distributed/ActorTransport.swift @@ -48,7 +48,7 @@ public protocol ActorTransport: Sendable { /// /// Detecting liveness of such remote actors shall be offered / by transport libraries /// by other means, such as "watching an actor for termination" or similar. - func resolve(_ identity: AnyActorIdentity, as actorType: Act.Type) throws -> Act? // TODO(distributed): make just optional + func resolve(_ identity: AnyActorIdentity, as actorType: Act.Type) throws -> Act? where Act: DistributedActor // ==== --------------------------------------------------------------------- diff --git a/stdlib/public/Distributed/DistributedActor.swift b/stdlib/public/Distributed/DistributedActor.swift index 0ab48d7df5687..3ea1568765830 100644 --- a/stdlib/public/Distributed/DistributedActor.swift +++ b/stdlib/public/Distributed/DistributedActor.swift @@ -93,16 +93,21 @@ extension DistributedActor { // ==== Codable conformance ---------------------------------------------------- extension CodingUserInfoKey { - @available(SwiftStdlib 5.5, *) - static let actorTransportKey = CodingUserInfoKey(rawValue: "$dist_act_transport")! + @available(SwiftStdlib 5.5, *) + public static let actorTransportKey = CodingUserInfoKey(rawValue: "$dist_act_transport")! } @available(SwiftStdlib 5.5, *) extension DistributedActor { nonisolated public init(from decoder: Decoder) throws { -// let id = try AnyActorIdentity(from: decoder) -// self = try Self.resolve(id, using: transport) // FIXME: This is going to be solved by the init() work!!!! - fatalError("\(#function) is not implemented yet for distributed actors'") + guard let transport = decoder.userInfo[.actorTransportKey] as? ActorTransport else { + throw DistributedActorCodingError(message: + "Missing ActorTransport (for key .actorTransportKey) " + + "in Decoder.userInfo, while decoding \(Self.self).") + } + + let id: AnyActorIdentity = try transport.decodeIdentity(from: decoder) + self = try Self.resolve(id, using: transport) } nonisolated public func encode(to encoder: Encoder) throws { @@ -121,12 +126,14 @@ public protocol ActorIdentity: Sendable, Hashable, Codable {} @available(SwiftStdlib 5.5, *) public struct AnyActorIdentity: ActorIdentity, @unchecked Sendable, CustomStringConvertible { + public let underlying: Any @usableFromInline let _hashInto: (inout Hasher) -> () @usableFromInline let _equalTo: (Any) -> Bool @usableFromInline let _encodeTo: (Encoder) throws -> () @usableFromInline let _description: () -> String public init(_ identity: ID) where ID: ActorIdentity { + self.underlying = identity _hashInto = { hasher in identity .hash(into: &hasher) } diff --git a/test/Distributed/Runtime/distributed_actor_decode.swift b/test/Distributed/Runtime/distributed_actor_decode.swift new file mode 100644 index 0000000000000..0346176368ed0 --- /dev/null +++ b/test/Distributed/Runtime/distributed_actor_decode.swift @@ -0,0 +1,218 @@ +// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-distributed -parse-as-library) | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: concurrency +// REQUIRES: distributed + +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime + +import _Distributed + +@available(SwiftStdlib 5.5, *) +distributed actor DA: CustomStringConvertible { + nonisolated var description: String { + "DA(\(self.id))" + } +} + +// ==== Fake Transport --------------------------------------------------------- + +@available(SwiftStdlib 5.5, *) +struct ActorAddress: ActorIdentity { + let address: String + init(parse address : String) { + self.address = address + } + + // Explicit implementations to make our TestEncoder/Decoder simpler + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.address = try container.decode(String.self) + print("decode ActorAddress -> \(self)") + } + + func encode(to encoder: Encoder) throws { + print("encode \(self)") + var container = encoder.singleValueContainer() + try container.encode(self.address) + } +} + +@available(SwiftStdlib 5.5, *) +struct FakeTransport: ActorTransport { + func decodeIdentity(from decoder: Decoder) throws -> AnyActorIdentity { + print("FakeTransport.decodeIdentity from:\(decoder)") + let address = try ActorAddress(from: decoder) + return AnyActorIdentity(address) + } + + func resolve(_ identity: AnyActorIdentity, as actorType: Act.Type) throws -> Act? + where Act: DistributedActor { + print("resolve type:\(actorType), address:\(identity)") + return nil + } + + func assignIdentity(_ actorType: Act.Type) -> AnyActorIdentity + where Act: DistributedActor { + let address = ActorAddress(parse: "xxx") + print("assign type:\(actorType), address:\(address)") + return .init(address) + } + + public func actorReady(_ actor: Act) where Act: DistributedActor { + print("ready actor:\(actor), address:\(actor.id)") + } + + func resignIdentity(_ identity: AnyActorIdentity) { + print("resign address:\(identity)") + } +} + +// ==== Test Coding ------------------------------------------------------------ + +@available(SwiftStdlib 5.5, *) +class TestEncoder: Encoder { + var codingPath: [CodingKey] + var userInfo: [CodingUserInfoKey: Any] + + var data: String? = nil + + init(transport: ActorTransport) { + self.codingPath = [] + self.userInfo = [.actorTransportKey: transport] + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer { + fatalError("Not implemented: \(#function)") + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + fatalError("Not implemented: \(#function)") + } + + func singleValueContainer() -> SingleValueEncodingContainer { + TestSingleValueEncodingContainer(parent: self) + } + + class TestSingleValueEncodingContainer: SingleValueEncodingContainer { + let parent: TestEncoder + init(parent: TestEncoder) { + self.parent = parent + } + + var codingPath: [CodingKey] = [] + + func encodeNil() throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: Bool) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: String) throws { + + } + func encode(_ value: Double) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: Float) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: Int) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: Int8) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: Int16) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: Int32) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: Int64) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: UInt) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: UInt8) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: UInt16) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: UInt32) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: UInt64) throws { fatalError("Not implemented: \(#function)") } + func encode(_ value: T) throws { + print("encode: \(value)") + if let identity = value as? AnyActorIdentity { + self.parent.data = + (identity.underlying as! ActorAddress).address + } + } + } + + func encode(_ actor: Act) throws -> String { + try actor.encode(to: self) + return self.data! + } +} + +@available(SwiftStdlib 5.5, *) +class TestDecoder: Decoder { + let encoder: TestEncoder + let data: String + + init(encoder: TestEncoder, transport: ActorTransport, data: String) { + self.encoder = encoder + self.userInfo = [.actorTransportKey: transport] + self.data = data + } + + var codingPath: [CodingKey] = [] + var userInfo: [CodingUserInfoKey : Any] + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + fatalError("Not implemented: \(#function)") + } + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + fatalError("Not implemented: \(#function)") + } + func singleValueContainer() throws -> SingleValueDecodingContainer { + TestSingleValueDecodingContainer(parent: self) + } + + class TestSingleValueDecodingContainer: SingleValueDecodingContainer { + let parent: TestDecoder + init(parent: TestDecoder) { + self.parent = parent + } + + var codingPath: [CodingKey] = [] + func decodeNil() -> Bool { fatalError("Not implemented: \(#function)") } + func decode(_ type: Bool.Type) throws -> Bool { fatalError("Not implemented: \(#function)") } + func decode(_ type: String.Type) throws -> String { + print("decode String -> \(self.parent.data)") + return self.parent.data + } + func decode(_ type: Double.Type) throws -> Double { fatalError("Not implemented: \(#function)") } + func decode(_ type: Float.Type) throws -> Float { fatalError("Not implemented: \(#function)") } + func decode(_ type: Int.Type) throws -> Int { fatalError("Not implemented: \(#function)") } + func decode(_ type: Int8.Type) throws -> Int8 { fatalError("Not implemented: \(#function)") } + func decode(_ type: Int16.Type) throws -> Int16 { fatalError("Not implemented: \(#function)") } + func decode(_ type: Int32.Type) throws -> Int32 { fatalError("Not implemented: \(#function)") } + func decode(_ type: Int64.Type) throws -> Int64 { fatalError("Not implemented: \(#function)") } + func decode(_ type: UInt.Type) throws -> UInt { fatalError("Not implemented: \(#function)") } + func decode(_ type: UInt8.Type) throws -> UInt8 { fatalError("Not implemented: \(#function)") } + func decode(_ type: UInt16.Type) throws -> UInt16 { fatalError("Not implemented: \(#function)") } + func decode(_ type: UInt32.Type) throws -> UInt32 { fatalError("Not implemented: \(#function)") } + func decode(_ type: UInt64.Type) throws -> UInt64 { fatalError("Not implemented: \(#function)") } + func decode(_ type: T.Type) throws -> T where T : Decodable { fatalError("Not implemented: \(#function)") } + } +} + +// ==== Execute ---------------------------------------------------------------- + +@available(SwiftStdlib 5.5, *) +func test() { + let transport = FakeTransport() + + // CHECK: assign type:DA, address:ActorAddress(address: "xxx") + let da = DA(transport: transport) + + // CHECK: encode: AnyActorIdentity(ActorAddress(address: "xxx")) + // CHECK: FakeTransport.decodeIdentity from:main.TestDecoder + let encoder = TestEncoder(transport: transport) + let data = try! encoder.encode(da) + + // CHECK: decode String -> xxx + // CHECK: decode ActorAddress -> ActorAddress(address: "xxx") + let da2 = try! DA(from: TestDecoder(encoder: encoder, transport: transport, data: data)) + + // CHECK: decoded da2: DA(AnyActorIdentity(ActorAddress(address: "xxx"))) + print("decoded da2: \(da2)") +} + +@available(SwiftStdlib 5.5, *) +@main struct Main { + static func main() async { + test() + } +}