From acb46227aa3f5ce03fabe3d768adad1571b75e5f Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 01/13] [AST] Introduce `isSpecialDistributedProperty` And use this in `evaluateMembersRequest`. --- include/swift/AST/Decl.h | 12 ++++++++++++ lib/AST/DistributedDecl.cpp | 29 +++++++++++++++++++++++++++++ lib/Sema/TypeCheckDecl.cpp | 17 +++++++++-------- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index e21becfbe2039..938a954fe6ece 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -374,6 +374,12 @@ bool conflicting(ASTContext &ctx, bool *wouldConflictInSwift5 = nullptr, bool skipProtocolExtensionCheck = false); +/// The kind of special compiler synthesized property in a \c distributed actor, +/// currently this includes \c id and \c actorSystem. +enum class SpecialDistributedProperty { + Id, ActorSystem +}; + /// The kind of artificial main to generate. enum class ArtificialMainKind : uint8_t { NSApplicationMain, @@ -3056,6 +3062,12 @@ class ValueDecl : public Decl { /// `distributed var get { }` accessors. bool isDistributedGetAccessor() const; + /// Whether this is the special synthesized 'id' or 'actorSystem' property + /// of a distributed actor. If \p onlyCheckName is set, then any + /// matching user-defined property with the name is also considered. + std::optional + isSpecialDistributedProperty(bool onlyCheckName = false) const; + bool hasName() const { return bool(Name); } bool isOperator() const { return Name.isOperator(); } diff --git a/lib/AST/DistributedDecl.cpp b/lib/AST/DistributedDecl.cpp index 3d9cf93799cc5..1495f625776d7 100644 --- a/lib/AST/DistributedDecl.cpp +++ b/lib/AST/DistributedDecl.cpp @@ -1367,6 +1367,35 @@ bool ValueDecl::isDistributedGetAccessor() const { return false; } +std::optional +ValueDecl::isSpecialDistributedProperty(bool onlyCheckName) const { + if (!onlyCheckName && !isSynthesized()) + return std::nullopt; + + if (!isa(this)) + return std::nullopt; + + auto &ctx = getASTContext(); + + auto kind = [&]() -> std::optional { + auto name = getName(); + if (name.isSimpleName(ctx.Id_id)) + return SpecialDistributedProperty::Id; + if (name.isSimpleName(ctx.Id_actorSystem)) + return SpecialDistributedProperty::ActorSystem; + + return std::nullopt; + }(); + if (!kind || onlyCheckName) + return kind; + + auto *NTD = getDeclContext()->getSelfNominalTypeDecl(); + if (!NTD || !NTD->isDistributedActor()) + return std::nullopt; + + return kind; +} + ConstructorDecl * NominalTypeDecl::getDistributedRemoteCallTargetInitFunction() const { auto mutableThis = const_cast(this); diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index f64ef9f8efc39..7d97e43388021 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -2905,6 +2905,10 @@ static ArrayRef evaluateMembersRequest( ResolveImplicitMemberRequest{nominal, ImplicitMemberAction::ResolveCodingKeys}, {}); + + // Synthesize distributed actor 'id' and 'actorSystem' if needed. + (void)nominal->getDistributedActorIDProperty(); + (void)nominal->getDistributedActorSystemProperty(); } // Expand synthesized member macros. @@ -2940,14 +2944,11 @@ static ArrayRef evaluateMembersRequest( if (auto *vd = dyn_cast(member)) { // Add synthesized members to a side table and sort them by their mangled // name, since they could have been added to the class in any order. - if (vd->isSynthesized() && - // FIXME: IRGen requires the distributed actor synthesized - // properties to be in a specific order that is different - // from ordering by their mangled name, so preserve the order - // they were added in. - !(nominal && - (vd == nominal->getDistributedActorIDProperty() || - vd == nominal->getDistributedActorSystemProperty()))) { + // FIXME: IRGen requires the distributed actor synthesized properties to + // be in a specific order that is different from ordering by their + // mangled name, so preserve the order + // they were added in. + if (vd->isSynthesized() && !vd->isSpecialDistributedProperty()) { synthesizedMembers.add(vd); return; } From 605d5f83bead085e35482a2ce835417e0eabe820 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 02/13] [Sema] Avoid inferring `associatedtype` from special distributed vars Avoid relying on the cycle detection logic to exclude these and enforce that we never consider them since computing their interface type relies on the type witnesses already being resolved. This also means we can no longer pick up Identifiable's default implementation `id`, which we never want for a `distributed` actor. This then lets us remove the special-cased eager inference logic. --- lib/Sema/AssociatedTypeInference.cpp | 48 +++++-------------- ...expansion_DistributedProtocol_errors.swift | 3 +- .../distributed_actor_system_missing.swift | 2 + ...ted_actor_system_missing_system_type.swift | 2 + ...d_actor_system_missing_type_no_crash.swift | 1 + ...uted_func_serialization_requirements.swift | 1 + 6 files changed, 19 insertions(+), 38 deletions(-) diff --git a/lib/Sema/AssociatedTypeInference.cpp b/lib/Sema/AssociatedTypeInference.cpp index f8d995762630b..d68474353d004 100644 --- a/lib/Sema/AssociatedTypeInference.cpp +++ b/lib/Sema/AssociatedTypeInference.cpp @@ -1238,19 +1238,6 @@ class AssociatedTypeInference { ArrayRef unresolvedAssocTypes, SmallVectorImpl &solutions); - /// We may need to determine a type witness, regardless of the existence of a - /// default value for it, e.g. when a 'distributed actor' is looking up its - /// 'ID', the default defined in an extension for 'Identifiable' would be - /// located using the lookup resolve. This would not be correct, since the - /// type actually must be based on the associated 'ActorSystem'. - /// - /// TODO(distributed): perhaps there is a better way to avoid this mixup? - /// Note though that this issue seems to only manifest in "real" builds - /// involving multiple files/modules, and not in tests within the Swift - /// project itself. - bool canAttemptEagerTypeWitnessDerivation( - DeclContext *DC, AssociatedTypeDecl *assocType); - public: /// Describes a mapping from associated type declarations to their /// type witnesses (as interface types). @@ -1669,6 +1656,17 @@ AssociatedTypeInference::getPotentialTypeWitnessesFromRequirement( }); } + // The `id` and `actorSystem` DistributedActor properties are never viable + // for type witness inference since their synthesis relies on the type + // witnesses already being resolved. + if (auto *nominal = dc->getSelfNominalTypeDecl()) { + if (nominal->isDistributedActor() && + req->isSpecialDistributedProperty(/*onlyCheckName*/ true)) { + LLVM_DEBUG(llvm::dbgs() << "skipping special distributed property\n"); + return {}; + } + } + TypeReprCycleCheckWalker cycleCheck(dc->getASTContext(), allUnresolved); InferredAssociatedTypesByWitnesses result; @@ -3990,20 +3988,6 @@ bool AssociatedTypeInference::diagnoseAmbiguousSolutions( return false; } -bool AssociatedTypeInference::canAttemptEagerTypeWitnessDerivation( - DeclContext *DC, AssociatedTypeDecl *assocType) { - - /// Rather than locating the TypeID via the default implementation of - /// Identifiable, we need to find the type based on the associated ActorSystem - if (auto *nominal = DC->getSelfNominalTypeDecl()) - if (nominal->isDistributedActor() && - assocType->getProtocol()->isSpecificProtocol(KnownProtocolKind::Identifiable)) { - return true; - } - - return false; -} - auto AssociatedTypeInference::solve() -> std::optional { LLVM_DEBUG(llvm::dbgs() << "============ Start " << conformance->getType() << ": " << conformance->getProtocol()->getName() @@ -4022,16 +4006,6 @@ auto AssociatedTypeInference::solve() -> std::optional { if (conformance->hasTypeWitness(assocType)) continue; - if (canAttemptEagerTypeWitnessDerivation(dc, assocType)) { - auto derivedType = computeDerivedTypeWitness(assocType); - if (derivedType.first) { - recordTypeWitness(conformance, assocType, - derivedType.first->mapTypeOutOfContext(), - derivedType.second); - continue; - } - } - // Try to resolve this type witness via name lookup, which is the // most direct mechanism, overriding all others. switch (resolveTypeWitnessViaLookup(conformance, assocType)) { diff --git a/test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_errors.swift b/test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_errors.swift index fe1278ebba830..3a2928a5fcd98 100644 --- a/test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_errors.swift +++ b/test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_errors.swift @@ -26,7 +26,7 @@ distributed actor Caplin { typealias ActorSystem = FakeActorSystem } -@Resolvable // expected-note 4{{in expansion of macro 'Resolvable' on protocol 'Fail' here}} +@Resolvable // expected-note 5{{in expansion of macro 'Resolvable' on protocol 'Fail' here}} protocol Fail: DistributedActor { distributed func method() -> String } @@ -36,6 +36,7 @@ expected-expansion@-2:2{{ expected-note@1:13{{you can provide a module-wide default actor system by declaring:}} expected-error@1:19{{type '$Fail' does not conform to protocol 'DistributedActor'}} expected-note@1:19{{add stubs for conformance}} + expected-error@1:19{{type '$Fail' does not conform to protocol 'Identifiable'}} }} */ diff --git a/test/Distributed/distributed_actor_system_missing.swift b/test/Distributed/distributed_actor_system_missing.swift index b5129ce8a5481..b25f3e90dd042 100644 --- a/test/Distributed/distributed_actor_system_missing.swift +++ b/test/Distributed/distributed_actor_system_missing.swift @@ -11,6 +11,7 @@ distributed actor DA { // expected-error@-2 {{type 'DA' does not conform to protocol 'DistributedActor'}} // expected-note@-3 {{add stubs for conformance}} // expected-note@-4{{you can provide a module-wide default actor system by declaring:}} + // expected-error@-5 {{type 'DA' does not conform to protocol 'Identifiable'}} // 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? @@ -24,6 +25,7 @@ distributed actor Server { // expected-error 2 {{distributed actor 'Server' does // 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@-3 {{add stubs for conformance}} + // expected-error@-4 {{type 'Server' does not conform to protocol 'Identifiable'}} typealias ActorSystem = DoesNotExistDataSystem // expected-error@-1{{cannot find type 'DoesNotExistDataSystem' in scope}} typealias SerializationRequirement = any Codable diff --git a/test/Distributed/distributed_actor_system_missing_system_type.swift b/test/Distributed/distributed_actor_system_missing_system_type.swift index 9984db3ea7a68..fa80ffcd759f0 100644 --- a/test/Distributed/distributed_actor_system_missing_system_type.swift +++ b/test/Distributed/distributed_actor_system_missing_system_type.swift @@ -14,6 +14,7 @@ distributed actor MyActor { // expected-note@-2{{you can provide a module-wide default actor system by declaring:}} // expected-error@-3{{type 'MyActor' does not conform to protocol 'DistributedActor'}} // expected-note@-4 {{add stubs for conformance}} + // expected-error@-5 {{type 'MyActor' does not conform to protocol 'Identifiable'}} distributed var foo: String { return "xyz" @@ -25,6 +26,7 @@ distributed actor BadSystemActor { // expected-note@-2{{you can provide a module-wide default actor system by declaring:}} // expected-error@-3{{type 'BadSystemActor' does not conform to protocol 'DistributedActor'}} // expected-note@-4 {{add stubs for conformance}} + // expected-error@-5 {{type 'BadSystemActor' does not conform to protocol 'Identifiable'}} // This system does not exist: but we should not crash, but just diagnose about it: typealias ActorSystem = ClusterSystem // expected-error{{cannot find type 'ClusterSystem' in scope}} diff --git a/test/Distributed/distributed_actor_system_missing_type_no_crash.swift b/test/Distributed/distributed_actor_system_missing_type_no_crash.swift index b6f52d384c478..65279eeff3dc4 100644 --- a/test/Distributed/distributed_actor_system_missing_type_no_crash.swift +++ b/test/Distributed/distributed_actor_system_missing_type_no_crash.swift @@ -13,6 +13,7 @@ distributed actor Fish { // 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@-5 {{add stubs for conformance}} + // expected-error@-6 {{type 'Fish' does not conform to protocol 'Identifiable'}} distributed func tell(_ text: String, by: Fish) { // What would the fish say, if it could talk? diff --git a/test/Distributed/distributed_protocols_distributed_func_serialization_requirements.swift b/test/Distributed/distributed_protocols_distributed_func_serialization_requirements.swift index c87c5b3b3aa69..6bd1953570e99 100644 --- a/test/Distributed/distributed_protocols_distributed_func_serialization_requirements.swift +++ b/test/Distributed/distributed_protocols_distributed_func_serialization_requirements.swift @@ -54,6 +54,7 @@ distributed actor ProtocolWithChecksSeqReqDA_MissingSystem: ProtocolWithChecksSe // // expected-error@-6{{type 'ProtocolWithChecksSeqReqDA_MissingSystem' does not conform to protocol 'DistributedActor'}} // expected-note@-7 {{add stubs for conformance}} + // expected-error@-8 {{type 'ProtocolWithChecksSeqReqDA_MissingSystem' does not conform to protocol 'Identifiable'}} // Entire conformance is doomed, so we didn't proceed to checking the functions; that's fine distributed func testAT() async throws -> NotCodable { .init() } From 72f4ca12f9d82ceb75325454e003c15eeae8a69c Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 03/13] [Sema] Remove unnecessary logic in `GetDistributedActorSystemPropertyRequest` This is checking a conformance for something defined in the Distributed module itself, so things would be very broken if it failed. --- lib/Sema/CodeSynthesisDistributedActor.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/Sema/CodeSynthesisDistributedActor.cpp b/lib/Sema/CodeSynthesisDistributedActor.cpp index 166a1a96ddbac..ae61adff7871f 100644 --- a/lib/Sema/CodeSynthesisDistributedActor.cpp +++ b/lib/Sema/CodeSynthesisDistributedActor.cpp @@ -974,8 +974,6 @@ VarDecl *GetDistributedActorSystemPropertyRequest::evaluate( Evaluator &evaluator, NominalTypeDecl *nominal) const { auto &C = nominal->getASTContext(); - auto DAS = C.getDistributedActorSystemDecl(); - // not via `ensureDistributedModuleLoaded` to avoid generating a warning, // we won't be emitting the offending decl after all. if (!C.getLoadedModule(C.Id_Distributed)) @@ -984,24 +982,6 @@ VarDecl *GetDistributedActorSystemPropertyRequest::evaluate( if (!nominal->isDistributedActor()) return nullptr; - if (auto proto = dyn_cast(nominal)) { - auto DistributedActorProto = C.getDistributedActorDecl(); - for (auto system : DistributedActorProto->lookupDirect(C.Id_actorSystem)) { - if (auto var = dyn_cast(system)) { - auto conformance = checkConformance( - proto->mapTypeIntoContext(var->getInterfaceType()), - DAS); - - if (conformance.isInvalid()) - continue; - - return var; - } - } - - return nullptr; - } - auto classDecl = dyn_cast(nominal); if (!classDecl) return nullptr; From f0df6bde1a3289213e1b190211240f3d3c1218bf Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 04/13] [AST] NFC: Reorder headers in `DistributedDecl.cpp` --- lib/AST/DistributedDecl.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/AST/DistributedDecl.cpp b/lib/AST/DistributedDecl.cpp index 1495f625776d7..2d048ef329f95 100644 --- a/lib/AST/DistributedDecl.cpp +++ b/lib/AST/DistributedDecl.cpp @@ -15,11 +15,11 @@ //===----------------------------------------------------------------------===// #include "swift/AST/DistributedDecl.h" -#include "swift/AST/AccessRequests.h" -#include "swift/AST/AccessScope.h" #include "swift/AST/ASTContext.h" -#include "swift/AST/ASTWalker.h" #include "swift/AST/ASTMangler.h" +#include "swift/AST/ASTWalker.h" +#include "swift/AST/AccessRequests.h" +#include "swift/AST/AccessScope.h" #include "swift/AST/ConformanceLookup.h" #include "swift/AST/ExistentialLayout.h" #include "swift/AST/Expr.h" @@ -29,7 +29,6 @@ #include "swift/AST/GenericSignature.h" #include "swift/AST/Initializer.h" #include "swift/AST/LazyResolver.h" -#include "swift/AST/ASTMangler.h" #include "swift/AST/Module.h" #include "swift/AST/NameLookup.h" #include "swift/AST/NameLookupRequests.h" @@ -39,9 +38,10 @@ #include "swift/AST/ResilienceExpansion.h" #include "swift/AST/SourceFile.h" #include "swift/AST/Stmt.h" -#include "swift/AST/TypeCheckRequests.h" #include "swift/AST/SwiftNameTranslation.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/Basic/Assertions.h" +#include "swift/Basic/StringExtras.h" #include "swift/ClangImporter/ClangModule.h" #include "swift/Parse/Lexer.h" // FIXME: Bad dependency #include "clang/Lex/MacroInfo.h" @@ -51,7 +51,6 @@ #include "llvm/ADT/Statistic.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/raw_ostream.h" -#include "swift/Basic/StringExtras.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/Module.h" From 613d47d188d63483a090075bd9f28d0d178a434f Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 05/13] [AST] Avoid returning null Type from `getAssociatedTypeOfDistributedSystemOfActor` Make sure we produce an error for a broken stdlib and produce ErrorType instead. --- include/swift/AST/DiagnosticsSema.def | 2 + lib/AST/DistributedDecl.cpp | 51 +++++++++++-------- lib/Sema/CodeSynthesisDistributedActor.cpp | 4 +- .../DerivedConformanceDistributedActor.cpp | 3 -- lib/Sema/TypeCheckDistributed.cpp | 21 +++----- 5 files changed, 40 insertions(+), 41 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 8344217e8d18e..ba121d384b9ad 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -5710,6 +5710,8 @@ ERROR(actor_cannot_inherit_distributed_actor_protocol,none, (DeclName)) ERROR(broken_distributed_actor_requirement,none, "DistributedActor protocol is broken: unexpected requirement", ()) +ERROR(broken_distributed_actor_system_requirement,none, + "DistributedActorSystem protocol is broken: unexpected requirement", ()) ERROR(distributed_actor_system_conformance_missing_adhoc_requirement,none, "%kind0 is missing witness for protocol requirement %1", (const ValueDecl *, DeclName)) diff --git a/lib/AST/DistributedDecl.cpp b/lib/AST/DistributedDecl.cpp index 2d048ef329f95..a871283154979 100644 --- a/lib/AST/DistributedDecl.cpp +++ b/lib/AST/DistributedDecl.cpp @@ -21,6 +21,7 @@ #include "swift/AST/AccessRequests.h" #include "swift/AST/AccessScope.h" #include "swift/AST/ConformanceLookup.h" +#include "swift/AST/DiagnosticsSema.h" #include "swift/AST/ExistentialLayout.h" #include "swift/AST/Expr.h" #include "swift/AST/ForeignAsyncConvention.h" @@ -197,7 +198,7 @@ Type swift::getDistributedActorSystemType(NominalTypeDecl *actor) { auto DA = C.getDistributedActorDecl(); if (!DA) - return ErrorType::get(C); // FIXME(distributed): just use Type() + return ErrorType::get(C); // Dig out the actor system type. Type selfType = actor->getSelfInterfaceType(); @@ -228,17 +229,16 @@ Type swift::getDistributedActorSerializationType( auto resultTy = getAssociatedTypeOfDistributedSystemOfActor( actorOrExtension, ctx.Id_SerializationRequirement); + if (resultTy->hasError()) + return resultTy; // Protocols are allowed to either not provide a `SerializationRequirement` // at all or provide it in a conformance requirement. - if ((!resultTy || resultTy->hasDependentMember()) && + if (resultTy->hasDependentMember() && actorOrExtension->getSelfProtocolDecl()) { auto sig = actorOrExtension->getGenericSignatureOfContext(); auto actorProtocol = ctx.getProtocol(KnownProtocolKind::DistributedActor); - if (!actorProtocol) - return Type(); - auto serializationTy = actorProtocol->getAssociatedType(ctx.Id_SerializationRequirement) ->getDeclaredInterfaceType(); @@ -330,29 +330,40 @@ swift::getAssociatedDistributedInvocationDecoderDecodeNextArgumentFunction( Type swift::getAssociatedTypeOfDistributedSystemOfActor( DeclContext *actorOrExtension, Identifier member) { auto &ctx = actorOrExtension->getASTContext(); + auto getLoc = [&]() { return extractNearestSourceLoc(actorOrExtension); }; auto actorProtocol = ctx.getProtocol(KnownProtocolKind::DistributedActor); - if (!actorProtocol) - return Type(); + if (!actorProtocol) { + ctx.Diags.diagnose(getLoc(), diag::broken_stdlib_type, "DistributedActor"); + return ErrorType::get(ctx); + } AssociatedTypeDecl *actorSystemDecl = actorProtocol->getAssociatedType(ctx.Id_ActorSystem); - if (!actorSystemDecl) - return Type(); + if (!actorSystemDecl) { + ctx.Diags.diagnose(getLoc(), diag::broken_distributed_actor_requirement); + return ErrorType::get(ctx); + } auto actorSystemProtocol = ctx.getDistributedActorSystemDecl(); - if (!actorSystemProtocol) - return Type(); + if (!actorSystemProtocol) { + ctx.Diags.diagnose(getLoc(), diag::broken_stdlib_type, + "DistributedActorSystem"); + return ErrorType::get(ctx); + } AssociatedTypeDecl *memberTypeDecl = actorSystemProtocol->getAssociatedType(member); - if (!memberTypeDecl) - return Type(); + if (!memberTypeDecl) { + ctx.Diags.diagnose(getLoc(), + diag::broken_distributed_actor_system_requirement); + return ErrorType::get(ctx); + } Type memberTy = DependentMemberType::get( - DependentMemberType::get(actorProtocol->getSelfInterfaceType(), - actorSystemDecl), - memberTypeDecl); + DependentMemberType::get(actorProtocol->getSelfInterfaceType(), + actorSystemDecl), + memberTypeDecl); auto sig = actorOrExtension->getGenericSignatureOfContext(); @@ -364,7 +375,7 @@ Type swift::getAssociatedTypeOfDistributedSystemOfActor( lookupConformance( actorType->getDeclaredInterfaceType(), actorProtocol); if (actorConformance.isInvalid()) - return Type(); + return ErrorType::get(ctx); auto subs = SubstitutionMap::getProtocolSubstitutions(actorConformance); @@ -431,11 +442,7 @@ swift::getDistributedSerializationRequirements( } bool swift::checkDistributedSerializationRequirementIsExactlyCodable( - ASTContext &C, - Type type) { - if (!type) - return false; - + ASTContext &C, Type type) { if (type->hasError()) return false; diff --git a/lib/Sema/CodeSynthesisDistributedActor.cpp b/lib/Sema/CodeSynthesisDistributedActor.cpp index ae61adff7871f..3211e3335a073 100644 --- a/lib/Sema/CodeSynthesisDistributedActor.cpp +++ b/lib/Sema/CodeSynthesisDistributedActor.cpp @@ -89,7 +89,7 @@ static VarDecl *addImplicitDistributedActorIDProperty( // ==== Synthesize and add 'id' property to the actor decl Type propertyType = getDistributedActorIDType(nominal); - if (!propertyType || propertyType->hasError()) + if (propertyType->hasError()) return nullptr; auto *propDecl = new (C) @@ -879,7 +879,7 @@ static bool canSynthesizeDistributedThunk(AbstractFunctionDecl *distributedTarge auto serializationTy = getDistributedActorSerializationType(distributedTarget->getDeclContext()); - return serializationTy && !serializationTy->hasDependentMember(); + return !serializationTy->hasError() && !serializationTy->hasDependentMember(); } /******************************************************************************/ diff --git a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp index 88a9358d45fca..2a12e7e2101bb 100644 --- a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp @@ -90,9 +90,6 @@ static FuncDecl *deriveDistributedActor_resolve(DerivedConformance &derived) { auto idType = getDistributedActorIDType(decl); auto actorSystemType = getDistributedActorSystemType(decl); - if (!idType || !actorSystemType) - return nullptr; - // (id: Self.ID, using system: Self.ActorSystem) auto *params = ParameterList::create( C, diff --git a/lib/Sema/TypeCheckDistributed.cpp b/lib/Sema/TypeCheckDistributed.cpp index 54ca3fd900676..ace36f9d25325 100644 --- a/lib/Sema/TypeCheckDistributed.cpp +++ b/lib/Sema/TypeCheckDistributed.cpp @@ -410,12 +410,8 @@ static bool checkDistributedTargetResultType( bool diagnose) { auto &C = valueDecl->getASTContext(); - if (serializationRequirement && serializationRequirement->hasError()) { - return false; - } - if (!serializationRequirement || serializationRequirement->hasError()) { + if (serializationRequirement->hasError()) return false; // error of the type would be diagnosed elsewhere - } Type resultType; if (auto func = dyn_cast(valueDecl)) { @@ -434,11 +430,8 @@ static bool checkDistributedTargetResultType( SmallVector serializationRequirements; // Collect extra "SerializationRequirement: SomeProtocol" requirements - if (serializationRequirement && !serializationRequirement->hasError()) { - auto srl = serializationRequirement->getExistentialLayout(); - llvm::copy(srl.getProtocols(), - std::back_inserter(serializationRequirements)); - } + auto srl = serializationRequirement->getExistentialLayout(); + llvm::copy(srl.getProtocols(), std::back_inserter(serializationRequirements)); auto isCodableRequirement = checkDistributedSerializationRequirementIsExactlyCodable( @@ -470,7 +463,7 @@ static bool checkDistributedTargetResultType( } } - return false; + return false; } bool swift::checkDistributedActorSystem(const NominalTypeDecl *system) { @@ -551,7 +544,7 @@ bool CheckDistributedFunctionRequest::evaluate( for (auto param: *func->getParameters()) { // --- Check the parameter conforming to serialization requirements - if (serializationReqType && !serializationReqType->hasError()) { + if (!serializationReqType->hasError()) { // If the requirement is exactly `Codable` we diagnose it ia bit nicer. auto serializationRequirementIsCodable = checkDistributedSerializationRequirementIsExactlyCodable( @@ -861,7 +854,7 @@ GetDistributedActorInvocationDecoderRequest::evaluate(Evaluator &evaluator, auto &ctx = actor->getASTContext(); auto decoderTy = getAssociatedTypeOfDistributedSystemOfActor( actor, ctx.Id_InvocationDecoder); - return decoderTy ? decoderTy->getAnyNominal() : nullptr; + return decoderTy->getAnyNominal(); } FuncDecl * @@ -886,7 +879,7 @@ GetDistributedActorConcreteArgumentDecodingMethodRequest::evaluate( auto serializationTy = getAssociatedTypeOfDistributedSystemOfActor( actor, ctx.Id_SerializationRequirement); - if (!serializationTy || !serializationTy->is()) + if (!serializationTy->is()) return nullptr; SmallVector serializationRequirements; From 48b6472b42419212e4190e34bb424228f964e9fe Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 06/13] [Sema] NFC: Inline `id` and `actorSystem` synthesis logic These can be inlined into their respective requests. --- lib/Sema/CodeSynthesisDistributedActor.cpp | 211 +++++++++------------ 1 file changed, 85 insertions(+), 126 deletions(-) diff --git a/lib/Sema/CodeSynthesisDistributedActor.cpp b/lib/Sema/CodeSynthesisDistributedActor.cpp index 3211e3335a073..0b996fe4ebbcf 100644 --- a/lib/Sema/CodeSynthesisDistributedActor.cpp +++ b/lib/Sema/CodeSynthesisDistributedActor.cpp @@ -73,112 +73,6 @@ static VarDecl* return var; } -// Note: This would be nice to implement in DerivedConformanceDistributedActor, -// but we can't since those are lazily triggered and an implementation exists -// for the 'id' property because 'Identifiable.id' has an extension that impls -// it for ObjectIdentifier, and we have to instead emit this stored property. -// -// The "derived" mechanisms are not really geared towards emitting for -// what already has a witness. -static VarDecl *addImplicitDistributedActorIDProperty( - ClassDecl *nominal) { - if (!nominal || !nominal->isDistributedActor()) - return nullptr; - - auto &C = nominal->getASTContext(); - - // ==== Synthesize and add 'id' property to the actor decl - Type propertyType = getDistributedActorIDType(nominal); - if (propertyType->hasError()) - return nullptr; - - auto *propDecl = new (C) - VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let, - SourceLoc(), C.Id_id, nominal); - propDecl->setImplicit(); - propDecl->setSynthesized(); - propDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true); - propDecl->setInterfaceType(propertyType); - - auto propContextTy = nominal->mapTypeIntoContext(propertyType); - - Pattern *propPat = NamedPattern::createImplicit(C, propDecl, propContextTy); - propPat = TypedPattern::createImplicit(C, propPat, propContextTy); - - PatternBindingDecl *pbDecl = PatternBindingDecl::createImplicit( - C, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr, - nominal); - - // mark as nonisolated, allowing access to it from everywhere - propDecl->addAttribute(NonisolatedAttr::createImplicit(C)); - // mark as @_compilerInitialized, since we synthesize the initializing - // assignment during SILGen. - propDecl->addAttribute(new (C) CompilerInitializedAttr(/*IsImplicit=*/true)); - - // IMPORTANT: The `id` MUST be the first field of any distributed actor, - // because when we allocate remote proxy instances, we don't allocate memory - // for anything except the first two fields: id and actorSystem, so they - // MUST be those fields. - // - // Their specific order also matters, because it is enforced this way in IRGen - // and how we emit them in AST MUST match what IRGen expects or cross-module - // things could be using wrong offsets and manifest as reading trash memory on - // id or system accesses. - nominal->addMember(propDecl, /*hint=*/nullptr, /*insertAtHead=*/true); - nominal->addMember(pbDecl, /*hint=*/nullptr, /*insertAtHead=*/true); - return propDecl; -} - -static VarDecl *addImplicitDistributedActorActorSystemProperty( - ClassDecl *nominal) { - if (!nominal) - return nullptr; - if (!nominal->isDistributedActor()) - return nullptr; - - auto &C = nominal->getASTContext(); - - // ==== Synthesize and add 'actorSystem' property to the actor decl - Type propertyType = getDistributedActorSystemType(nominal); - - auto *propDecl = new (C) - VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let, - SourceLoc(), C.Id_actorSystem, nominal); - propDecl->setImplicit(); - propDecl->setSynthesized(); - propDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true); - propDecl->setInterfaceType(propertyType); - - auto propContextTy = nominal->mapTypeIntoContext(propertyType); - - Pattern *propPat = NamedPattern::createImplicit(C, propDecl, propContextTy); - propPat = TypedPattern::createImplicit(C, propPat, propContextTy); - - PatternBindingDecl *pbDecl = PatternBindingDecl::createImplicit( - C, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr, - nominal); - - // mark as nonisolated, allowing access to it from everywhere - propDecl->addAttribute(NonisolatedAttr::createImplicit(C)); - - auto idProperty = nominal->getDistributedActorIDProperty(); - // If the id was not yet synthesized, we need to ensure that eventually - // the order of fields will be: id, actorSystem (because IRGen needs the - // layouts to match with the AST we produce). We do this by inserting FIRST, - // and then as the ID gets synthesized, it'll also force FIRST and therefore - // the order will be okey -- ID and then system. - auto insertAtHead = idProperty == nullptr; - - // IMPORTANT: The `id` MUST be the first field of any distributed actor. - // So we find the property and add the system AFTER it using the hint. - // - // If the `id` was not synthesized yet, we'll end up inserting at head, - // but the id synthesis will force itself to be FIRST anyway, so it works out. - nominal->addMember(propDecl, /*hint=*/idProperty, /*insertAtHead=*/insertAtHead); - nominal->addMember(pbDecl, /*hint=*/idProperty, /*insertAtHead=*/insertAtHead); - return propDecl; -} - /******************************************************************************/ /*********************** DISTRIBUTED THUNK SYNTHESIS **************************/ /******************************************************************************/ @@ -944,46 +838,74 @@ FuncDecl *GetDistributedThunkRequest::evaluate(Evaluator &evaluator, llvm_unreachable("Unable to synthesize distributed thunk"); } -VarDecl *GetDistributedActorIDPropertyRequest::evaluate( - Evaluator &evaluator, NominalTypeDecl *actor) const { - if (!actor->isDistributedActor()) - return nullptr; - - auto &C = actor->getASTContext(); - +VarDecl * +GetDistributedActorIDPropertyRequest::evaluate(Evaluator &evaluator, + NominalTypeDecl *nominal) const { // not via `ensureDistributedModuleLoaded` to avoid generating a warning, // we won't be emitting the offending decl after all. + auto &C = nominal->getASTContext(); if (!C.getLoadedModule(C.Id_Distributed)) return nullptr; - auto classDecl = dyn_cast(actor); - if (!classDecl) + if (!isa(nominal) || !nominal->isDistributedActor()) return nullptr; // We may enter this request multiple times, e.g. in multi-file projects, // so in order to avoid synthesizing a property many times, first perform // a lookup and return if it already exists. - if (auto existingProp = lookupDistributedActorProperty(classDecl, C.Id_id)) { + if (auto existingProp = lookupDistributedActorProperty(nominal, C.Id_id)) { return existingProp; } - return addImplicitDistributedActorIDProperty(classDecl); + // ==== Synthesize and add 'id' property to the actor decl + Type propertyType = getDistributedActorIDType(nominal); + if (propertyType->hasError()) + return nullptr; + + auto *propDecl = new (C) VarDecl(/*IsStatic*/ false, VarDecl::Introducer::Let, + SourceLoc(), C.Id_id, nominal); + propDecl->setImplicit(); + propDecl->setSynthesized(); + propDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true); + propDecl->setInterfaceType(propertyType); + + auto propContextTy = nominal->mapTypeIntoContext(propertyType); + + Pattern *propPat = NamedPattern::createImplicit(C, propDecl, propContextTy); + propPat = TypedPattern::createImplicit(C, propPat, propContextTy); + + PatternBindingDecl *pbDecl = PatternBindingDecl::createImplicit( + C, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr, nominal); + + // mark as nonisolated, allowing access to it from everywhere + propDecl->addAttribute(NonisolatedAttr::createImplicit(C)); + // mark as @_compilerInitialized, since we synthesize the initializing + // assignment during SILGen. + propDecl->addAttribute(new (C) CompilerInitializedAttr(/*IsImplicit=*/true)); + + // IMPORTANT: The `id` MUST be the first field of any distributed actor, + // because when we allocate remote proxy instances, we don't allocate memory + // for anything except the first two fields: id and actorSystem, so they + // MUST be those fields. + // + // Their specific order also matters, because it is enforced this way in IRGen + // and how we emit them in AST MUST match what IRGen expects or cross-module + // things could be using wrong offsets and manifest as reading trash memory on + // id or system accesses. + nominal->addMember(propDecl, /*hint=*/nullptr, /*insertAtHead=*/true); + nominal->addMember(pbDecl, /*hint=*/nullptr, /*insertAtHead=*/true); + return propDecl; } VarDecl *GetDistributedActorSystemPropertyRequest::evaluate( Evaluator &evaluator, NominalTypeDecl *nominal) const { - auto &C = nominal->getASTContext(); - // not via `ensureDistributedModuleLoaded` to avoid generating a warning, // we won't be emitting the offending decl after all. + auto &C = nominal->getASTContext(); if (!C.getLoadedModule(C.Id_Distributed)) return nullptr; - if (!nominal->isDistributedActor()) - return nullptr; - - auto classDecl = dyn_cast(nominal); - if (!classDecl) + if (!isa(nominal) || !nominal->isDistributedActor()) return nullptr; // We may be triggered after synthesis was handled via `DerivedConformances`, @@ -992,11 +914,48 @@ VarDecl *GetDistributedActorSystemPropertyRequest::evaluate( // but for some reason sometimes we get a request before synthesis was triggered // there... so this is to workaround that issue, and ensure we're always // synthesising correctly, regardless of entry-point. - if (auto existingProp = lookupDistributedActorProperty(classDecl, C.Id_actorSystem)) { + if (auto existingProp = + lookupDistributedActorProperty(nominal, C.Id_actorSystem)) { return existingProp; } - return addImplicitDistributedActorActorSystemProperty(classDecl); + // ==== Synthesize and add 'actorSystem' property to the actor decl + Type propertyType = getDistributedActorSystemType(nominal); + + auto *propDecl = new (C) VarDecl(/*IsStatic*/ false, VarDecl::Introducer::Let, + SourceLoc(), C.Id_actorSystem, nominal); + propDecl->setImplicit(); + propDecl->setSynthesized(); + propDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true); + propDecl->setInterfaceType(propertyType); + + auto propContextTy = nominal->mapTypeIntoContext(propertyType); + + Pattern *propPat = NamedPattern::createImplicit(C, propDecl, propContextTy); + propPat = TypedPattern::createImplicit(C, propPat, propContextTy); + + PatternBindingDecl *pbDecl = PatternBindingDecl::createImplicit( + C, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr, nominal); + + // mark as nonisolated, allowing access to it from everywhere + propDecl->addAttribute(NonisolatedAttr::createImplicit(C)); + + auto idProperty = nominal->getDistributedActorIDProperty(); + // If the id was not yet synthesized, we need to ensure that eventually + // the order of fields will be: id, actorSystem (because IRGen needs the + // layouts to match with the AST we produce). We do this by inserting FIRST, + // and then as the ID gets synthesized, it'll also force FIRST and therefore + // the order will be okey -- ID and then system. + auto insertAtHead = idProperty == nullptr; + + // IMPORTANT: The `id` MUST be the first field of any distributed actor. + // So we find the property and add the system AFTER it using the hint. + // + // If the `id` was not synthesized yet, we'll end up inserting at head, + // but the id synthesis will force itself to be FIRST anyway, so it works out. + nominal->addMember(propDecl, /*hint=*/idProperty, insertAtHead); + nominal->addMember(pbDecl, /*hint=*/idProperty, insertAtHead); + return propDecl; } NormalProtocolConformance *GetDistributedActorImplicitCodableRequest::evaluate( From b79b5598a7740018aeb50bc8949148802722e373 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 07/13] [Sema] Consolidate distributed actor property synthesis logic Have the witness derivation logic call into the synthesis requests. --- .../DerivedConformanceDistributedActor.cpp | 62 +------------------ 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp index 2a12e7e2101bb..add96634a166c 100644 --- a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp @@ -453,70 +453,12 @@ static FuncDecl *deriveDistributedActorSystem_invokeHandlerOnReturn( /******************************************************************************/ static ValueDecl *deriveDistributedActor_id(DerivedConformance &derived) { - assert(derived.Nominal->isDistributedActor()); - auto &C = derived.Context; - - // ``` - // nonisolated let id: Self.ID // Self.ActorSystem.ActorID - // ``` - auto propertyType = getDistributedActorIDType(derived.Nominal); - - VarDecl *propDecl; - PatternBindingDecl *pbDecl; - std::tie(propDecl, pbDecl) = derived.declareDerivedProperty( - DerivedConformance::SynthesizedIntroducer::Let, C.Id_id, propertyType, - /*isStatic=*/false, /*isFinal=*/true); - - // mark as nonisolated, allowing access to it from everywhere - propDecl->addAttribute(NonisolatedAttr::createImplicit(C)); - - derived.addMemberToConformanceContext(pbDecl, /*insertAtHead=*/true); - derived.addMemberToConformanceContext(propDecl, /*insertAtHead=*/true); - return propDecl; + return derived.Nominal->getDistributedActorIDProperty(); } static ValueDecl *deriveDistributedActor_actorSystem( DerivedConformance &derived) { - auto &C = derived.Context; - - auto classDecl = dyn_cast(derived.Nominal); - assert(classDecl && derived.Nominal->isDistributedActor()); - - if (!C.getLoadedModule(C.Id_Distributed)) - return nullptr; - - // ``` - // nonisolated let actorSystem: ActorSystem - // ``` - // (no need for @actorIndependent because it is an immutable let) - auto propertyType = getDistributedActorSystemType(classDecl); - - VarDecl *propDecl; - PatternBindingDecl *pbDecl; - std::tie(propDecl, pbDecl) = derived.declareDerivedProperty( - DerivedConformance::SynthesizedIntroducer::Let, C.Id_actorSystem, - propertyType, /*isStatic=*/false, /*isFinal=*/true); - - // mark as nonisolated, allowing access to it from everywhere - propDecl->addAttribute(NonisolatedAttr::createImplicit(C)); - - // IMPORTANT: `id` MUST be the first field of a distributed actor, and - // `actorSystem` MUST be the second field, because for a remote instance - // we don't allocate memory after those two fields, so their order is very - // important. The `hint` below makes sure the system is inserted right after. - if (auto id = derived.Nominal->getDistributedActorIDProperty()) { - derived.addMemberToConformanceContext(propDecl, /*hint=*/id); - derived.addMemberToConformanceContext(pbDecl, /*hint=*/id); - } else { - // `id` will be synthesized next, and will insert at head, - // so in order for system to be SECOND (as it must be), - // we'll insert at head right now and as id gets synthesized we'll get - // the correct order: id, actorSystem. - derived.addMemberToConformanceContext(propDecl, /*insertAtHead=*/true); - derived.addMemberToConformanceContext(pbDecl, /*insertAtHead==*/true); - } - - return propDecl; + return derived.Nominal->getDistributedActorSystemProperty(); } /******************************************************************************/ From b996e0f1875e1709b3908500f49c40c2c160ed4d Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 08/13] [Sema] Always synthesize `id` and `actorSystem` properties Only fallback to name lookup for serialized modules and swift interfaces. This means we can then implement the user-declared `id`/`actorSystem` error as part of redeclaration checking. --- lib/Sema/CodeSynthesisDistributedActor.cpp | 81 +++++++------------ lib/Sema/TypeCheckDeclPrimary.cpp | 11 ++- lib/Sema/TypeCheckDistributed.cpp | 34 -------- lib/Sema/TypeCheckDistributed.h | 3 - .../protocol/special/DistributedActor.swift | 25 +++++- 5 files changed, 60 insertions(+), 94 deletions(-) diff --git a/lib/Sema/CodeSynthesisDistributedActor.cpp b/lib/Sema/CodeSynthesisDistributedActor.cpp index 0b996fe4ebbcf..176a891c21a0c 100644 --- a/lib/Sema/CodeSynthesisDistributedActor.cpp +++ b/lib/Sema/CodeSynthesisDistributedActor.cpp @@ -35,44 +35,6 @@ using namespace swift; -/******************************************************************************/ -/************************ PROPERTY SYNTHESIS **********************************/ -/******************************************************************************/ - -static VarDecl* - lookupDistributedActorProperty(NominalTypeDecl *decl, DeclName name) { - assert(decl && "decl was null"); - auto &C = decl->getASTContext(); - - auto clazz = dyn_cast(decl); - if (!clazz) - return nullptr; - - auto refs = decl->lookupDirect(name); - if (refs.size() != 1) - return nullptr; - - auto var = dyn_cast(refs.front()); - if (!var) - return nullptr; - - Type expectedType = Type(); - if (name == C.Id_id) { - expectedType = getDistributedActorIDType(decl); - } else if (name == C.Id_actorSystem) { - expectedType = getDistributedActorSystemType(decl); - } else { - llvm_unreachable("Unexpected distributed actor property lookup!"); - } - if (!expectedType) - return nullptr; - - if (!var->getInterfaceType()->isEqual(expectedType)) - return nullptr; - - return var; - } - /******************************************************************************/ /*********************** DISTRIBUTED THUNK SYNTHESIS **************************/ /******************************************************************************/ @@ -838,6 +800,23 @@ FuncDecl *GetDistributedThunkRequest::evaluate(Evaluator &evaluator, llvm_unreachable("Unable to synthesize distributed thunk"); } +static VarDecl *lookupDistributedActorProperty(NominalTypeDecl *decl, + DeclName name) { + VarDecl *result = nullptr; + for (auto *ref : decl->lookupDirect(name)) { + auto *prop = dyn_cast(ref); + if (!prop || prop->getDeclContext() != decl) + continue; + + if (!result) { + result = prop; + continue; + } + return nullptr; + } + return result; +} + VarDecl * GetDistributedActorIDPropertyRequest::evaluate(Evaluator &evaluator, NominalTypeDecl *nominal) const { @@ -850,12 +829,11 @@ GetDistributedActorIDPropertyRequest::evaluate(Evaluator &evaluator, if (!isa(nominal) || !nominal->isDistributedActor()) return nullptr; - // We may enter this request multiple times, e.g. in multi-file projects, - // so in order to avoid synthesizing a property many times, first perform - // a lookup and return if it already exists. - if (auto existingProp = lookupDistributedActorProperty(nominal, C.Id_id)) { - return existingProp; - } + // If we're in a deserialized module or swift interface we expect to be able + // to find this through name lookup. + auto *DC = nominal->getDeclContext(); + if (!DC->getParentSourceFile() || DC->isInSwiftinterface()) + return lookupDistributedActorProperty(nominal, C.Id_id); // ==== Synthesize and add 'id' property to the actor decl Type propertyType = getDistributedActorIDType(nominal); @@ -908,16 +886,11 @@ VarDecl *GetDistributedActorSystemPropertyRequest::evaluate( if (!isa(nominal) || !nominal->isDistributedActor()) return nullptr; - // We may be triggered after synthesis was handled via `DerivedConformances`, - // in which case we should locate the existing property, rather than add - // another one. Generally derived conformances are triggered early and are right - // but for some reason sometimes we get a request before synthesis was triggered - // there... so this is to workaround that issue, and ensure we're always - // synthesising correctly, regardless of entry-point. - if (auto existingProp = - lookupDistributedActorProperty(nominal, C.Id_actorSystem)) { - return existingProp; - } + // If we're in a deserialized module or swift interface we expect to be able + // to find this through name lookup. + auto *DC = nominal->getDeclContext(); + if (!DC->getParentSourceFile() || DC->isInSwiftinterface()) + return lookupDistributedActorProperty(nominal, C.Id_actorSystem); // ==== Synthesize and add 'actorSystem' property to the actor decl Type propertyType = getDistributedActorSystemType(nominal); diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index cbe192dbb3aac..0c773a9a19b74 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -1071,8 +1071,15 @@ CheckRedeclarationRequest::evaluate(Evaluator &eval, ValueDecl *current, return req->getName() == VD->getName(); }); } - declToDiagnose->diagnose(diag::invalid_redecl_implicit, current, - isProtocolRequirement, other); + auto *conflictDecl = current == declToDiagnose ? other : current; + if (conflictDecl->isSpecialDistributedProperty()) { + declToDiagnose->diagnose( + diag::distributed_actor_user_defined_special_property, + other->getName()); + } else { + declToDiagnose->diagnose(diag::invalid_redecl_implicit, current, + isProtocolRequirement, other); + } // Emit a specialized note if the one of the declarations is // the backing storage property ('_foo') or projected value diff --git a/lib/Sema/TypeCheckDistributed.cpp b/lib/Sema/TypeCheckDistributed.cpp index ace36f9d25325..397db5990f629 100644 --- a/lib/Sema/TypeCheckDistributed.cpp +++ b/lib/Sema/TypeCheckDistributed.cpp @@ -658,39 +658,6 @@ bool swift::checkDistributedActorProperty(VarDecl *var, bool diagnose) { return false; } -void swift::checkDistributedActorProperties(const NominalTypeDecl *decl) { - auto &C = decl->getASTContext(); - - if (!decl->getDeclContext()->getParentSourceFile()) { - // Don't diagnose when checking without source file (e.g. from module, importer etc). - return; - } - - if (decl->getDeclContext()->isInSwiftinterface()) { - // Don't diagnose properties in swiftinterfaces. - return; - } - - if (isa(decl)) { - // protocols don't matter for stored property checking - return; - } - - for (auto member : decl->getMembers()) { - if (auto prop = dyn_cast(member)) { - if (prop->isSynthesized()) - continue; - - auto id = prop->getName(); - if (id == C.Id_actorSystem || id == C.Id_id) { - prop->diagnose(diag::distributed_actor_user_defined_special_property, - id); - prop->setInvalid(); - } - } - } -} - // ==== ------------------------------------------------------------------------ void TypeChecker::checkDistributedActor(SourceFile *SF, NominalTypeDecl *nominal) { @@ -758,7 +725,6 @@ void TypeChecker::checkDistributedActor(SourceFile *SF, NominalTypeDecl *nominal } // ==== Properties - checkDistributedActorProperties(nominal); // --- Synthesize the 'id' property here rather than via derived conformance // because the 'DerivedConformanceDistributedActor' won't trigger for 'id' // because it has a default impl via 'Identifiable' (ObjectIdentifier) diff --git a/lib/Sema/TypeCheckDistributed.h b/lib/Sema/TypeCheckDistributed.h index 18f8c0b391d56..20864e01698eb 100644 --- a/lib/Sema/TypeCheckDistributed.h +++ b/lib/Sema/TypeCheckDistributed.h @@ -37,9 +37,6 @@ class NominalTypeDecl; // Diagnose an error if the Distributed module is not loaded. bool ensureDistributedModuleLoaded(const ValueDecl *decl); -/// Check for illegal property declarations (e.g. re-declaring transport or id) -void checkDistributedActorProperties(const NominalTypeDecl *decl); - /// Type-check additional ad-hoc protocol requirements. /// Ad-hoc requirements are protocol requirements currently not expressible /// in the Swift type-system. diff --git a/test/decl/protocol/special/DistributedActor.swift b/test/decl/protocol/special/DistributedActor.swift index a3efe499a8390..f268a058497f2 100644 --- a/test/decl/protocol/special/DistributedActor.swift +++ b/test/decl/protocol/special/DistributedActor.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -disable-availability-checking -verify-ignore-unknown +// RUN: %target-typecheck-verify-swift -disable-availability-checking -verify-ignore-unknown -verify-ignore-unrelated // REQUIRES: concurrency // REQUIRES: distributed @@ -67,6 +67,29 @@ distributed actor D5: P1 { nonisolated distributed actor D6 {} // expected-error {{'nonisolated' modifier cannot be applied to this declaration}}{{1-13=}} +// Can't define `id` and `actorSystem` in an extension either. +distributed actor D7 {} +extension D7 { + nonisolated var id: String { fatalError() } + // expected-error@-1 {{property 'id' cannot be defined explicitly, as it conflicts with distributed actor synthesized stored property}} + + nonisolated var actorSystem: OtherActorIdentity { fatalError() } + // expected-error@-1 {{property 'actorSystem' cannot be defined explicitly, as it conflicts with distributed actor synthesized stored property}} +} + +// FIXME: Unfortunately redeclaration checking is run after conformance checking +// so we also get a conformace error here. +distributed actor D8 {} // expected-error {{type 'D8' does not conform to protocol 'DistributedActor'}} +extension D8 { + nonisolated var id: ID { fatalError() } + // expected-error@-1 {{property 'id' cannot be defined explicitly, as it conflicts with distributed actor synthesized stored property}} + // expected-note@-2 {{candidate exactly matches}} + + nonisolated var actorSystem: DefaultDistributedActorSystem { fatalError() } + // expected-error@-1 {{property 'actorSystem' cannot be defined explicitly, as it conflicts with distributed actor synthesized stored property}} + // expected-note@-2 {{candidate exactly matches}} +} + // ==== Tests ------------------------------------------------------------------ // Make sure the conformances have been added implicitly. From 66aaf4db393bd2e861d71cd917c8076bd96264fb Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 09/13] [Sema] Lazily compute type of `id` and `actorSystem` properties Delay their interface type computation until they are actually type-checked. This ensures their synthesis logic doesn't produce cycles. --- lib/Sema/CodeSynthesisDistributedActor.cpp | 18 +------- lib/Sema/TypeCheckStorage.cpp | 49 ++++++++++++++++++++-- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/lib/Sema/CodeSynthesisDistributedActor.cpp b/lib/Sema/CodeSynthesisDistributedActor.cpp index 176a891c21a0c..2ee00b12efddd 100644 --- a/lib/Sema/CodeSynthesisDistributedActor.cpp +++ b/lib/Sema/CodeSynthesisDistributedActor.cpp @@ -836,21 +836,13 @@ GetDistributedActorIDPropertyRequest::evaluate(Evaluator &evaluator, return lookupDistributedActorProperty(nominal, C.Id_id); // ==== Synthesize and add 'id' property to the actor decl - Type propertyType = getDistributedActorIDType(nominal); - if (propertyType->hasError()) - return nullptr; - auto *propDecl = new (C) VarDecl(/*IsStatic*/ false, VarDecl::Introducer::Let, SourceLoc(), C.Id_id, nominal); propDecl->setImplicit(); propDecl->setSynthesized(); propDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true); - propDecl->setInterfaceType(propertyType); - - auto propContextTy = nominal->mapTypeIntoContext(propertyType); - Pattern *propPat = NamedPattern::createImplicit(C, propDecl, propContextTy); - propPat = TypedPattern::createImplicit(C, propPat, propContextTy); + Pattern *propPat = NamedPattern::createImplicit(C, propDecl); PatternBindingDecl *pbDecl = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr, nominal); @@ -893,19 +885,13 @@ VarDecl *GetDistributedActorSystemPropertyRequest::evaluate( return lookupDistributedActorProperty(nominal, C.Id_actorSystem); // ==== Synthesize and add 'actorSystem' property to the actor decl - Type propertyType = getDistributedActorSystemType(nominal); - auto *propDecl = new (C) VarDecl(/*IsStatic*/ false, VarDecl::Introducer::Let, SourceLoc(), C.Id_actorSystem, nominal); propDecl->setImplicit(); propDecl->setSynthesized(); propDecl->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/ true); - propDecl->setInterfaceType(propertyType); - - auto propContextTy = nominal->mapTypeIntoContext(propertyType); - Pattern *propPat = NamedPattern::createImplicit(C, propDecl, propContextTy); - propPat = TypedPattern::createImplicit(C, propPat, propContextTy); + Pattern *propPat = NamedPattern::createImplicit(C, propDecl); PatternBindingDecl *pbDecl = PatternBindingDecl::createImplicit( C, StaticSpellingKind::None, propPat, /*InitExpr*/ nullptr, nominal); diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 4bbb1e5f49477..80a2d5bfdbd60 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -29,6 +29,7 @@ #include "swift/AST/DeclExportabilityVisitor.h" #include "swift/AST/DiagnosticsParse.h" #include "swift/AST/DiagnosticsSema.h" +#include "swift/AST/DistributedDecl.h" #include "swift/AST/Expr.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/Initializer.h" @@ -406,16 +407,58 @@ MemberwiseInitPropertiesRequest::evaluate(Evaluator &evaluator, return decl->getASTContext().AllocateCopy(results); } +static Type getLazyInterfaceTypeForSynthesizedVar(VarDecl *var) { + // For DistributedActor, the `id` and `actorSystem` properties have their + // types computed lazily. + if (auto distPropKind = var->isSpecialDistributedProperty()) { + auto *NTD = var->getDeclContext()->getSelfNominalTypeDecl(); + ASSERT(NTD); + switch (distPropKind.value()) { + case SpecialDistributedProperty::Id: + return getDistributedActorIDType(NTD); + case SpecialDistributedProperty::ActorSystem: + return getDistributedActorSystemType(NTD); + } + llvm_unreachable("Unhandled case in switch!"); + } + return Type(); +} + +static Pattern * +getLazilySynthesizedPattern(PatternBindingDecl *PBD, Pattern *P) { + // Check to see if we have a pattern that binds a single synthesized var. + auto *NP = dyn_cast(P->getSemanticsProvidingPattern()); + if (!NP) + return P; + + auto *var = NP->getDecl(); + if (!var->isSynthesized()) + return P; + + auto interfaceTy = getLazyInterfaceTypeForSynthesizedVar(var); + if (!interfaceTy) + return P; + + auto *DC = var->getDeclContext(); + auto &ctx = DC->getASTContext(); + auto patternTy = DC->mapTypeIntoContext(interfaceTy); + return TypedPattern::createImplicit(ctx, P, patternTy); +} + /// Validate the \c entryNumber'th entry in \c binding. const PatternBindingEntry *PatternBindingEntryRequest::evaluate( Evaluator &eval, PatternBindingDecl *binding, unsigned entryNumber) const { const auto &pbe = binding->getPatternList()[entryNumber]; auto &Context = binding->getASTContext(); + // Resolve a lazily synthesized pattern if necessary. + auto *pattern = binding->getPattern(entryNumber); + pattern = getLazilySynthesizedPattern(binding, pattern); + binding->setPattern(entryNumber, pattern); + // Resolve the pattern. - auto *pattern = TypeChecker::resolvePattern(binding->getPattern(entryNumber), - binding->getDeclContext(), - /*isStmtCondition*/ true); + pattern = TypeChecker::resolvePattern(pattern, binding->getDeclContext(), + /*isStmtCondition*/ true); if (!pattern) { binding->setInvalid(); binding->getPattern(entryNumber)->setType(ErrorType::get(Context)); From 44891f6ce1c24443118d5ca318cc7f05a21f8db5 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 10/13] [Sema] Install `id`/`actorSystem` in `synthesizeSemanticMembersIfNeeded` Now that the synthesis logic for these doesn't involve kicking semantic requests we can synthesize directly as part of name lookup. This ensures they get synthesized for secondaries and we don't try and use Identifiable's default `id`. rdar://162800185 --- include/swift/AST/TypeCheckRequests.h | 2 -- lib/AST/Decl.cpp | 16 ++++++++++---- lib/AST/TypeCheckRequests.cpp | 6 ------ lib/Sema/CodeSynthesis.cpp | 6 ++---- .../DerivedConformance/DerivedConformance.cpp | 8 ------- .../DerivedConformanceDistributedActor.cpp | 20 +----------------- lib/Sema/TypeCheckDistributed.cpp | 9 -------- test/Distributed/rdar162800185.swift | 21 +++++++++++++++++++ .../verificationFailure-94e552.swift | 10 +++++++++ 9 files changed, 46 insertions(+), 52 deletions(-) create mode 100644 test/Distributed/rdar162800185.swift create mode 100644 validation-test/compiler_crashers_fixed/verificationFailure-94e552.swift diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 928484825f3b1..14e6c19af4805 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3000,8 +3000,6 @@ enum class ImplicitMemberAction : uint8_t { ResolveEncodable, ResolveDecodable, ResolveDistributedActor, - ResolveDistributedActorID, - ResolveDistributedActorSystem, }; class ResolveImplicitMemberRequest diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index a187dcff91ace..ecc7e804da2cc 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -6449,6 +6449,16 @@ void NominalTypeDecl::synthesizeSemanticMembersIfNeeded(DeclName member) { if (isa(this)) return; + auto baseName = member.getBaseName(); + auto &Context = getASTContext(); + + // For a distributed actor `id` and `actorSystem` can be synthesized without + // causing cycles so do them above the cycle guard. + if (member.isSimpleName(Context.Id_id)) + (void)getDistributedActorIDProperty(); + if (member.isSimpleName(Context.Id_actorSystem)) + (void)getDistributedActorSystemProperty(); + // Silently break cycles here because we can't be sure when and where a // request to synthesize will come from yet. // FIXME: rdar://56844567 @@ -6458,14 +6468,12 @@ void NominalTypeDecl::synthesizeSemanticMembersIfNeeded(DeclName member) { Bits.NominalTypeDecl.IsComputingSemanticMembers = true; SWIFT_DEFER { Bits.NominalTypeDecl.IsComputingSemanticMembers = false; }; - auto baseName = member.getBaseName(); - auto &Context = getASTContext(); std::optional action = std::nullopt; if (baseName.isConstructor()) action.emplace(ImplicitMemberAction::ResolveImplicitInit); if (member.isSimpleName() && !baseName.isSpecial()) { - if (baseName.getIdentifier() == getASTContext().Id_CodingKeys) { + if (baseName.getIdentifier() == Context.Id_CodingKeys) { action.emplace(ImplicitMemberAction::ResolveCodingKeys); } } else { @@ -6475,7 +6483,7 @@ void NominalTypeDecl::synthesizeSemanticMembersIfNeeded(DeclName member) { if ((member.isSimpleName() || argumentNames.front() == Context.Id_from)) { action.emplace(ImplicitMemberAction::ResolveDecodable); } else if (argumentNames.front() == Context.Id_system) { - action.emplace(ImplicitMemberAction::ResolveDistributedActorSystem); + action.emplace(ImplicitMemberAction::ResolveDistributedActor); } } else if (!baseName.isSpecial() && baseName.getIdentifier() == Context.Id_encode && diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 96836b24a1c6d..9cce3bc5841ce 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1313,12 +1313,6 @@ void swift::simple_display(llvm::raw_ostream &out, case ImplicitMemberAction::ResolveDistributedActor: out << "resolve DistributedActor"; break; - case ImplicitMemberAction::ResolveDistributedActorID: - out << "resolve DistributedActor.id"; - break; - case ImplicitMemberAction::ResolveDistributedActorSystem: - out << "resolve DistributedActor.actorSystem"; - break; } } diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 7c86d441b379e..7b9e361ff1847 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1451,15 +1451,13 @@ ResolveImplicitMemberRequest::evaluate(Evaluator &evaluator, (void)evaluateTargetConformanceTo(decodableProto); } break; - case ImplicitMemberAction::ResolveDistributedActor: - case ImplicitMemberAction::ResolveDistributedActorSystem: - case ImplicitMemberAction::ResolveDistributedActorID: { + case ImplicitMemberAction::ResolveDistributedActor: { // init(transport:) and init(resolve:using:) may be synthesized as part of // derived conformance to the DistributedActor protocol. // If the target should conform to the DistributedActor protocol, check the // conformance here to attempt synthesis. // FIXME(distributed): invoke the requirement adding explicitly here - TypeChecker::addImplicitConstructors(target); + TypeChecker::addImplicitConstructors(target); auto *distributedActorProto = Context.getProtocol(KnownProtocolKind::DistributedActor); (void)evaluateTargetConformanceTo(distributedActorProto); diff --git a/lib/Sema/DerivedConformance/DerivedConformance.cpp b/lib/Sema/DerivedConformance/DerivedConformance.cpp index 216c3f6f18ad3..797f94536b171 100644 --- a/lib/Sema/DerivedConformance/DerivedConformance.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformance.cpp @@ -353,14 +353,6 @@ ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal, } } - // DistributedActor.id - if (name.isSimpleName(ctx.Id_id)) - return getRequirement(KnownProtocolKind::DistributedActor); - - // DistributedActor.actorSystem - if (name.isSimpleName(ctx.Id_actorSystem)) - return getRequirement(KnownProtocolKind::DistributedActor); - return nullptr; } diff --git a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp index add96634a166c..b3d663de76061 100644 --- a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp @@ -448,19 +448,6 @@ static FuncDecl *deriveDistributedActorSystem_invokeHandlerOnReturn( return funcDecl; } -/******************************************************************************/ -/******************************* PROPERTIES ***********************************/ -/******************************************************************************/ - -static ValueDecl *deriveDistributedActor_id(DerivedConformance &derived) { - return derived.Nominal->getDistributedActorIDProperty(); -} - -static ValueDecl *deriveDistributedActor_actorSystem( - DerivedConformance &derived) { - return derived.Nominal->getDistributedActorSystemProperty(); -} - /******************************************************************************/ /***************************** ASSOC TYPES ************************************/ /******************************************************************************/ @@ -788,13 +775,8 @@ static ValueDecl *deriveDistributedActor_unownedExecutor(DerivedConformance &der ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) { if (auto var = dyn_cast(requirement)) { ValueDecl *derivedValue = nullptr; - if (var->getName() == Context.Id_id) { - derivedValue = deriveDistributedActor_id(*this); - } else if (var->getName() == Context.Id_actorSystem) { - derivedValue = deriveDistributedActor_actorSystem(*this); - } else if (var->getName() == Context.Id_unownedExecutor) { + if (var->getName() == Context.Id_unownedExecutor) derivedValue = deriveDistributedActor_unownedExecutor(*this); - } if (derivedValue) { assertRequiredSynthesizedPropertyOrder(Context, Nominal); diff --git a/lib/Sema/TypeCheckDistributed.cpp b/lib/Sema/TypeCheckDistributed.cpp index 397db5990f629..0be7c0530a426 100644 --- a/lib/Sema/TypeCheckDistributed.cpp +++ b/lib/Sema/TypeCheckDistributed.cpp @@ -723,15 +723,6 @@ void TypeChecker::checkDistributedActor(SourceFile *SF, NominalTypeDecl *nominal } } } - - // ==== Properties - // --- Synthesize the 'id' property here rather than via derived conformance - // because the 'DerivedConformanceDistributedActor' won't trigger for 'id' - // because it has a default impl via 'Identifiable' (ObjectIdentifier) - // which we do not want. - // Also, the 'id' var must be added before the 'actorSystem'. - // See NOTE (id-before-actorSystem) for more details. - (void)nominal->getDistributedActorIDProperty(); } bool TypeChecker::checkDistributedFunc(FuncDecl *func) { diff --git a/test/Distributed/rdar162800185.swift b/test/Distributed/rdar162800185.swift new file mode 100644 index 0000000000000..8d8e276b65ebd --- /dev/null +++ b/test/Distributed/rdar162800185.swift @@ -0,0 +1,21 @@ +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// Make sure we can resolve the conformance to DistributedActor from a secondary. +// RUN: %target-swift-frontend -c -module-name main -target %target-swift-5.7-abi-triple %t/a.swift %t/b.swift -o %/tmp +// RUN: %target-swift-frontend -c -module-name main -target %target-swift-5.7-abi-triple -primary-file %t/a.swift %t/b.swift -o %/tmp +// RUN: %target-swift-frontend -c -module-name main -target %target-swift-5.7-abi-triple %t/a.swift -primary-file %t/b.swift -o %/tmp + +// REQUIRES: concurrency +// REQUIRES: distributed + +//--- a.swift +import Distributed + +distributed actor A {} + +//--- b.swift +import Distributed + +func foo(_: T.Type) where T.ActorSystem == LocalTestingDistributedActorSystem {} +func bar() { foo(A.self) } diff --git a/validation-test/compiler_crashers_fixed/verificationFailure-94e552.swift b/validation-test/compiler_crashers_fixed/verificationFailure-94e552.swift new file mode 100644 index 0000000000000..26d3d09581371 --- /dev/null +++ b/validation-test/compiler_crashers_fixed/verificationFailure-94e552.swift @@ -0,0 +1,10 @@ +// {"kind":"emit-silgen","signature":"swift::verificationFailure(llvm::Twine const&, swift::SILInstruction const*, swift::SILArgument const*, llvm::function_ref)"} +// RUN: not %target-swift-frontend -emit-silgen %s +// REQUIRES: OS=macosx +import Distributed +distributed actor a { +} +extension a { + nonisolated var id: ActorSystem.ActorID { + } +} From fc70a7ac3ed89ef723da2faea723052e8f2903db Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 12:52:24 +0000 Subject: [PATCH 11/13] [Sema] Remove Identifiable derivation for distributed actors This shouldn't be necessary anymore since we we'll no longer try to infer `ID` from Identifiable's default implmentation of `id`. --- include/swift/AST/Decl.h | 1 - lib/AST/Decl.cpp | 2 - lib/Sema/AssociatedTypeInference.cpp | 6 --- .../DerivedConformance/DerivedConformance.cpp | 2 - .../DerivedConformance/DerivedConformance.h | 4 -- .../DerivedConformanceDistributedActor.cpp | 37 ------------------- lib/Sema/TypeCheckProtocol.cpp | 9 ----- 7 files changed, 61 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 938a954fe6ece..c0ea831296f01 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -5442,7 +5442,6 @@ enum class KnownDerivableProtocolKind : uint8_t { Decodable, AdditiveArithmetic, Differentiable, - Identifiable, Actor, DistributedActor, DistributedActorSystem, diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index ecc7e804da2cc..ee1f03968d0d0 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -7562,8 +7562,6 @@ ProtocolDecl::getKnownDerivableProtocolKind() const { return KnownDerivableProtocolKind::AdditiveArithmetic; case KnownProtocolKind::Differentiable: return KnownDerivableProtocolKind::Differentiable; - case KnownProtocolKind::Identifiable: - return KnownDerivableProtocolKind::Identifiable; case KnownProtocolKind::Actor: return KnownDerivableProtocolKind::Actor; case KnownProtocolKind::DistributedActor: diff --git a/lib/Sema/AssociatedTypeInference.cpp b/lib/Sema/AssociatedTypeInference.cpp index d68474353d004..407d22e483967 100644 --- a/lib/Sema/AssociatedTypeInference.cpp +++ b/lib/Sema/AssociatedTypeInference.cpp @@ -2633,12 +2633,6 @@ deriveTypeWitness(const NormalProtocolConformance *Conformance, return derived.deriveDifferentiable(AssocType); case KnownProtocolKind::DistributedActor: return derived.deriveDistributedActor(AssocType); - case KnownProtocolKind::Identifiable: - // Identifiable only has derivation logic for distributed actors, - // because how it depends on the ActorSystem the actor is associated with. - // If the nominal wasn't a distributed actor, we should not end up here, - // but either way, then we'd return null (fail derivation). - return derived.deriveDistributedActor(AssocType); default: return std::make_pair(nullptr, nullptr); } diff --git a/lib/Sema/DerivedConformance/DerivedConformance.cpp b/lib/Sema/DerivedConformance/DerivedConformance.cpp index 797f94536b171..2b8df914ddcf6 100644 --- a/lib/Sema/DerivedConformance/DerivedConformance.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformance.cpp @@ -99,8 +99,6 @@ bool DerivedConformance::derivesProtocolConformance( if (*derivableKind == KnownDerivableProtocolKind::Actor) return canDeriveActor(DC, Nominal); - if (*derivableKind == KnownDerivableProtocolKind::Identifiable) - return canDeriveIdentifiable(Nominal, DC); if (*derivableKind == KnownDerivableProtocolKind::DistributedActor) return canDeriveDistributedActor(Nominal, DC); if (*derivableKind == KnownDerivableProtocolKind::DistributedActorSystem) diff --git a/lib/Sema/DerivedConformance/DerivedConformance.h b/lib/Sema/DerivedConformance/DerivedConformance.h index 39c8110c5c19f..cf98052613047 100644 --- a/lib/Sema/DerivedConformance/DerivedConformance.h +++ b/lib/Sema/DerivedConformance/DerivedConformance.h @@ -324,10 +324,6 @@ class DerivedConformance { /// \returns the derived member, which will also be added to the type. ValueDecl *deriveDecodable(ValueDecl *requirement); - /// Identifiable may need to have the `ID` type witness synthesized explicitly - static bool canDeriveIdentifiable(NominalTypeDecl *nominal, - DeclContext *dc); - /// Whether we can derive the given DistributedActor requirement in the given context. static bool canDeriveDistributedActor(NominalTypeDecl *nominal, DeclContext *dc); diff --git a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp index b3d663de76061..45682a0864894 100644 --- a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp @@ -29,19 +29,6 @@ using namespace swift; -bool DerivedConformance::canDeriveIdentifiable( - NominalTypeDecl *nominal, DeclContext *dc) { - // we only synthesize for concrete 'distributed actor' decls (which are class) - if (!isa(nominal)) - return false; - - auto &C = nominal->getASTContext(); - if (!C.getLoadedModule(C.Id_Distributed)) - return false; - - return nominal->isDistributedActor(); -} - bool DerivedConformance::canDeriveDistributedActor( NominalTypeDecl *nominal, DeclContext *dc) { auto &C = nominal->getASTContext(); @@ -484,26 +471,6 @@ deriveDistributedActorType_ActorSystem( return defaultDistributedActorSystemTypeDecl->getDeclaredInterfaceType(); } -static Type -deriveDistributedActorType_ID( - DerivedConformance &derived) { - if (!derived.Nominal->isDistributedActor()) - return nullptr; - - // Look for a type DefaultDistributedActorSystem within the parent context. - auto systemTy = getDistributedActorSystemType(derived.Nominal); - - // There is no known actor system type, so fail to synthesize. - if (!systemTy || systemTy->hasError()) - return nullptr; - - if (auto systemNominal = systemTy->getAnyNominal()) { - return getDistributedActorSystemActorIDType(systemNominal); - } - - return nullptr; -} - static Type deriveDistributedActorType_SerializationRequirement( DerivedConformance &derived) { @@ -810,10 +777,6 @@ std::pair DerivedConformance::deriveDistributedActor( deriveDistributedActorType_SerializationRequirement(*this), nullptr); } - if (assocType->getName() == Context.Id_ID) { - return std::make_pair(deriveDistributedActorType_ID(*this), nullptr); - } - Context.Diags.diagnose(assocType->getLoc(), diag::broken_distributed_actor_requirement); return std::make_pair(Type(), nullptr); diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 9f4bf072c2d25..615275a6d29dc 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -4712,15 +4712,6 @@ deriveProtocolRequirement(const NormalProtocolConformance *Conformance, case KnownDerivableProtocolKind::Differentiable: return derived.deriveDifferentiable(Requirement); - case KnownDerivableProtocolKind::Identifiable: - if (derived.Nominal->isDistributedActor()) { - return derived.deriveDistributedActor(Requirement); - } else { - // No synthesis is required for other types; we should only end up - // attempting synthesis if the nominal was a distributed actor. - llvm_unreachable("Identifiable is synthesized for distributed actors"); - } - case KnownDerivableProtocolKind::DistributedActor: return derived.deriveDistributedActor(Requirement); From 5aa0a7424327ffea275c8c2f92c172747d9ed29e Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Sun, 2 Nov 2025 16:03:11 +0000 Subject: [PATCH 12/13] [Sema] Remove `ImplicitMemberAction::ResolveDistributedActor` The member names that triggered this don't even match the requirements in the Distributed module anymore, and `installDistributedActorIfNecessary` was only called for structs which can't be distributed actors. Rip it out. --- include/swift/AST/TypeCheckRequests.h | 1 - lib/AST/Decl.cpp | 9 --------- lib/AST/TypeCheckRequests.cpp | 3 --- lib/Sema/CodeSynthesis.cpp | 12 ------------ lib/Sema/TypeCheckDeclPrimary.cpp | 8 -------- 5 files changed, 33 deletions(-) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 14e6c19af4805..477e557325195 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -2999,7 +2999,6 @@ enum class ImplicitMemberAction : uint8_t { ResolveCodingKeys, ResolveEncodable, ResolveDecodable, - ResolveDistributedActor, }; class ResolveImplicitMemberRequest diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index ee1f03968d0d0..dd1ca33bbf77d 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -6482,8 +6482,6 @@ void NominalTypeDecl::synthesizeSemanticMembersIfNeeded(DeclName member) { if (baseName.isConstructor()) { if ((member.isSimpleName() || argumentNames.front() == Context.Id_from)) { action.emplace(ImplicitMemberAction::ResolveDecodable); - } else if (argumentNames.front() == Context.Id_system) { - action.emplace(ImplicitMemberAction::ResolveDistributedActor); } } else if (!baseName.isSpecial() && baseName.getIdentifier() == Context.Id_encode && @@ -6491,13 +6489,6 @@ void NominalTypeDecl::synthesizeSemanticMembersIfNeeded(DeclName member) { argumentNames.front() == Context.Id_to)) { action.emplace(ImplicitMemberAction::ResolveEncodable); } - } else if (member.isSimpleName() || argumentNames.size() == 2) { - if (baseName.isConstructor()) { - if (argumentNames[0] == Context.Id_resolve && - argumentNames[1] == Context.Id_using) { - action.emplace(ImplicitMemberAction::ResolveDistributedActor); - } - } } } diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 9cce3bc5841ce..fbb490e0932ed 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1310,9 +1310,6 @@ void swift::simple_display(llvm::raw_ostream &out, case ImplicitMemberAction::ResolveDecodable: out << "resolve Decodable.init(from:)"; break; - case ImplicitMemberAction::ResolveDistributedActor: - out << "resolve DistributedActor"; - break; } } diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 7b9e361ff1847..8adecd324e952 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -1449,18 +1449,6 @@ ResolveImplicitMemberRequest::evaluate(Evaluator &evaluator, TypeChecker::addImplicitConstructors(target); auto *decodableProto = Context.getProtocol(KnownProtocolKind::Decodable); (void)evaluateTargetConformanceTo(decodableProto); - } - break; - case ImplicitMemberAction::ResolveDistributedActor: { - // init(transport:) and init(resolve:using:) may be synthesized as part of - // derived conformance to the DistributedActor protocol. - // If the target should conform to the DistributedActor protocol, check the - // conformance here to attempt synthesis. - // FIXME(distributed): invoke the requirement adding explicitly here - TypeChecker::addImplicitConstructors(target); - auto *distributedActorProto = - Context.getProtocol(KnownProtocolKind::DistributedActor); - (void)evaluateTargetConformanceTo(distributedActorProto); break; } } diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index 0c773a9a19b74..f3f692a166281 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -461,13 +461,6 @@ static void installCodingKeysIfNecessary(NominalTypeDecl *NTD) { (void)evaluateOrDefault(NTD->getASTContext().evaluator, req, {}); } -// TODO(distributed): same ugly hack as Codable does... -static void installDistributedActorIfNecessary(NominalTypeDecl *NTD) { - auto req = - ResolveImplicitMemberRequest{NTD, ImplicitMemberAction::ResolveDistributedActor}; - (void)evaluateOrDefault(NTD->getASTContext().evaluator, req, {}); -} - // Check for static properties that produce empty option sets // using a rawValue initializer with a value of '0' static void checkForEmptyOptionSet(const VarDecl *VD) { @@ -3089,7 +3082,6 @@ class DeclChecker : public DeclVisitor { TypeChecker::addImplicitConstructors(SD); installCodingKeysIfNecessary(SD); - installDistributedActorIfNecessary(SD); TypeChecker::checkDeclAttributes(SD); From 965e85f9a3e3983b6043cb772410d86e76ae8272 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Mon, 3 Nov 2025 12:29:56 +0000 Subject: [PATCH 13/13] [AST] Replace a couple more checks for vars named `id`/`actorSystem` Use `isSpecialDistributedProperty` or the synthesis requests instead. While here, also make `isSpecialDistributedProperty` agnostic to interface and serialized module cases. --- lib/AST/DistributedDecl.cpp | 19 +++++++++++++------ lib/SILGen/SILGenDestructor.cpp | 4 +--- lib/Sema/TypeCheckAttr.cpp | 3 +-- lib/Sema/TypeCheckStorage.cpp | 28 ++++------------------------ 4 files changed, 19 insertions(+), 35 deletions(-) diff --git a/lib/AST/DistributedDecl.cpp b/lib/AST/DistributedDecl.cpp index a871283154979..52036473dacf5 100644 --- a/lib/AST/DistributedDecl.cpp +++ b/lib/AST/DistributedDecl.cpp @@ -1375,13 +1375,11 @@ bool ValueDecl::isDistributedGetAccessor() const { std::optional ValueDecl::isSpecialDistributedProperty(bool onlyCheckName) const { - if (!onlyCheckName && !isSynthesized()) - return std::nullopt; - if (!isa(this)) return std::nullopt; - auto &ctx = getASTContext(); + auto *DC = getDeclContext(); + auto &ctx = DC->getASTContext(); auto kind = [&]() -> std::optional { auto name = getName(); @@ -1395,10 +1393,19 @@ ValueDecl::isSpecialDistributedProperty(bool onlyCheckName) const { if (!kind || onlyCheckName) return kind; - auto *NTD = getDeclContext()->getSelfNominalTypeDecl(); - if (!NTD || !NTD->isDistributedActor()) + // The properties can only be synthesized in the nominal itself. + auto *CD = dyn_cast(DC); + if (!CD || !CD->isDistributedActor()) return std::nullopt; + // The synthesized bit doesn't get preserved by serialization or module + // interfaces, we only need to check it when compiling a SourceFile though + // since we'll diagnose any conflicting user-defined versions. + if (!isSynthesized() && DC->getParentSourceFile() && + !DC->isInSwiftinterface()) { + return std::nullopt; + } + return kind; } diff --git a/lib/SILGen/SILGenDestructor.cpp b/lib/SILGen/SILGenDestructor.cpp index 0ae90b2db278c..d80309d21a61d 100644 --- a/lib/SILGen/SILGenDestructor.cpp +++ b/lib/SILGen/SILGenDestructor.cpp @@ -74,10 +74,8 @@ void SILGenFunction::emitDistributedRemoteActorDeinit( continue; // Just to double-check, we only want to destroy `id` and `actorSystem` - if (vd->getBaseIdentifier() == C.Id_id || - vd->getBaseIdentifier() == C.Id_actorSystem) { + if (vd->isSpecialDistributedProperty()) destroyClassMember(cleanupLoc, borrowedSelf, vd); - } } if (cd->isRootDefaultActor()) { diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 7856a8927541f..24538a19b4b93 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -7852,8 +7852,7 @@ void AttributeChecker::visitNonisolatedAttr(NonisolatedAttr *attr) { // The synthesized "id" and "actorSystem" are the only exceptions, // because the implementation mirrors them. if (nominal->isDistributedActor() && - !(var->getName() == Ctx.Id_id || - var->getName() == Ctx.Id_actorSystem)) { + !var->isSpecialDistributedProperty()) { diagnoseAndRemoveAttr(attr, diag::nonisolated_distributed_actor_storage); return; diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 80a2d5bfdbd60..abee23507a823 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -213,30 +213,10 @@ static void enumerateStoredPropertiesAndMissing( // If we have a distributed actor, find the id and actorSystem // properties. We always want them first, and in a specific // order. - if (decl->isDistributedActor()) { - VarDecl *distributedActorId = nullptr; - VarDecl *distributedActorSystem = nullptr; - ASTContext &ctx = decl->getASTContext(); - for (auto *member : implDecl->getMembers()) { - if (auto *var = dyn_cast(member)) { - if (!var->isStatic() && var->hasStorage()) { - if (var->getName() == ctx.Id_id) { - distributedActorId = var; - } else if (var->getName() == ctx.Id_actorSystem) { - distributedActorSystem = var; - } - } - - if (distributedActorId && distributedActorSystem) - break; - } - } - - if (distributedActorId) - addStoredProperty(distributedActorId); - if (distributedActorSystem) - addStoredProperty(distributedActorSystem); - } + if (auto idVar = decl->getDistributedActorIDProperty()) + addStoredProperty(idVar); + if (auto actorSystemVar = decl->getDistributedActorSystemProperty()) + addStoredProperty(actorSystemVar); for (auto *member : implDecl->getMembers()) { if (auto *var = dyn_cast(member)) {