Skip to content

Commit 2ea1421

Browse files
committed
[SE-0470] Fix synthesized conformances with default main actor isolation
When defaulting to main-actor isolation, types that have synthesized conformances (e.g., for Equatable, Hashable, Codable) were getting nonisolated members by default. That would cause compiler errors because the conformances themselves defaulted to main-actor isolation when their types were. Be careful to only mark these members as 'nonisolated' when it makes sense, and leave them to get the isolation of their enclosing type when the conformance might have isolation. This ensures that one can use synthesis of these protocols along with default main-actor mode. There is a one-off trick here to force the synthesized CodingKeys to be nonisolated, because the CodingKey protocol requires Sendable. We'll separately consider whether to generalize this rule. More of rdar://150691429.
1 parent a3a17d0 commit 2ea1421

File tree

6 files changed

+91
-2
lines changed

6 files changed

+91
-2
lines changed

include/swift/AST/ProtocolConformance.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,9 @@ class NormalProtocolConformance : public RootProtocolConformance,
717717
return ExplicitSafety::Unspecified;
718718
}
719719

720+
/// Whether this conformance has explicitly-specified global actor isolation.
721+
bool hasExplicitGlobalActorIsolation() const;
722+
720723
/// Determine whether we've lazily computed the associated conformance array
721724
/// already.
722725
bool hasComputedAssociatedConformances() const {

lib/AST/ProtocolConformance.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,10 @@ TypeExpr *NormalProtocolConformance::getExplicitGlobalActorIsolation() const {
515515
return ctx.getGlobalCache().conformanceExplicitGlobalActorIsolation[this];
516516
}
517517

518+
bool NormalProtocolConformance::hasExplicitGlobalActorIsolation() const {
519+
return Bits.NormalProtocolConformance.HasExplicitGlobalActor;
520+
}
521+
518522
void
519523
NormalProtocolConformance::setExplicitGlobalActorIsolation(TypeExpr *typeExpr) {
520524
if (!typeExpr) {

lib/Sema/CodeSynthesis.cpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1728,12 +1728,52 @@ bool swift::hasLetStoredPropertyWithInitialValue(NominalTypeDecl *nominal) {
17281728
});
17291729
}
17301730

1731+
/// Determine whether a synth
1732+
static bool synthesizedRequirementIsNonIsolated(
1733+
const NormalProtocolConformance *conformance) {
1734+
// @preconcurrency suppresses this.
1735+
if (conformance->isPreconcurrency())
1736+
return false;
1737+
1738+
// Explicit global actor isolation suppresses this.
1739+
if (conformance->hasExplicitGlobalActorIsolation())
1740+
return false;
1741+
1742+
// Explicit nonisolated forces this.
1743+
if (conformance->getOptions()
1744+
.contains(ProtocolConformanceFlags::Nonisolated))
1745+
return true;
1746+
1747+
// When we are inferring conformance isolation, only add nonisolated if
1748+
// either
1749+
// (1) the protocol inherits from SendableMetatype, or
1750+
// (2) the conforming type is nonisolated.
1751+
ASTContext &ctx = conformance->getDeclContext()->getASTContext();
1752+
if (!ctx.LangOpts.hasFeature(Feature::InferIsolatedConformances))
1753+
return true;
1754+
1755+
// Check inheritance from SendableMetatype, which implies that the conformance
1756+
// will be nonisolated.
1757+
auto sendableMetatypeProto =
1758+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
1759+
if (sendableMetatypeProto &&
1760+
conformance->getProtocol()->inheritsFrom(sendableMetatypeProto))
1761+
return true;
1762+
1763+
auto nominalType = conformance->getType()->getAnyNominal();
1764+
if (!nominalType)
1765+
return true;
1766+
1767+
return !getActorIsolation(nominalType).isMainActor();
1768+
}
1769+
17311770
bool swift::addNonIsolatedToSynthesized(DerivedConformance &derived,
17321771
ValueDecl *value) {
17331772
if (auto *conformance = derived.Conformance) {
1734-
if (conformance && conformance->isPreconcurrency())
1773+
if (!synthesizedRequirementIsNonIsolated(conformance))
17351774
return false;
17361775
}
1776+
17371777
return addNonIsolatedToSynthesized(derived.Nominal, value);
17381778
}
17391779

lib/Sema/DerivedConformance/DerivedConformanceCodable.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,16 @@ addImplicitCodingKeys(NominalTypeDecl *target,
155155
enumDecl->setSynthesized();
156156
enumDecl->setAccess(AccessLevel::Private);
157157

158+
switch (C.LangOpts.DefaultIsolationBehavior) {
159+
case DefaultIsolation::MainActor:
160+
enumDecl->getAttrs().add(NonisolatedAttr::createImplicit(C));
161+
break;
162+
163+
case DefaultIsolation::Nonisolated:
164+
// Nothing to do.
165+
break;
166+
}
167+
158168
// For classes which inherit from something Encodable or Decodable, we
159169
// provide case `super` as the first key (to be used in encoding super).
160170
auto *classDecl = dyn_cast<ClassDecl>(target);

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7988,16 +7988,24 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance
79887988

79897989
auto dc = rootNormal->getDeclContext();
79907990
ASTContext &ctx = dc->getASTContext();
7991+
auto proto = rootNormal->getProtocol();
79917992

79927993
// If the protocol itself is isolated, don't infer isolation for the
79937994
// conformance.
7994-
if (getActorIsolation(rootNormal->getProtocol()).isActorIsolated())
7995+
if (getActorIsolation(proto).isActorIsolated())
7996+
return ActorIsolation::forNonisolated(false);
7997+
7998+
// SendableMetatype disables isolation inference.
7999+
auto sendableMetatypeProto =
8000+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
8001+
if (sendableMetatypeProto && proto->inheritsFrom(sendableMetatypeProto))
79958002
return ActorIsolation::forNonisolated(false);
79968003

79978004
// @preconcurrency disables isolation inference.
79988005
if (rootNormal->isPreconcurrency())
79998006
return ActorIsolation::forNonisolated(false);
80008007

8008+
80018009
// Isolation inference rules follow. If we aren't inferring isolated conformances,
80028010
// we're done.
80038011
if (!ctx.LangOpts.hasFeature(Feature::InferIsolatedConformances))

test/Concurrency/isolated_conformance_default_actor.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ nonisolated class CNonIsolated: P {
4242
@MainActor func f() { } // expected-note{{main actor-isolated instance method 'f()' cannot satisfy nonisolated requirement}}
4343
}
4444

45+
// Synthesized conformances
46+
struct EquatableStruct: Equatable {
47+
var state: Int = 0
48+
}
49+
50+
struct HashableStruct: Hashable {
51+
var state: Int = 0
52+
}
53+
54+
enum RawRepresentableEnum: Int {
55+
case one
56+
case two
57+
}
58+
59+
class CodableClass: Codable {
60+
var state: Int = 0
61+
}
62+
4563
func acceptSendablePMeta<T: Sendable & P>(_: T.Type) { }
4664
func acceptSendableQMeta<T: Sendable & Q>(_: T.Type) { }
4765

@@ -55,4 +73,10 @@ nonisolated func testConformancesFromNonisolated() {
5573
// Okay, these are nonisolated conformances.
5674
let _: any Q = CExplicitMainActor()
5775
let _: any Q = CImplicitMainActor()
76+
77+
// Error, these are main-actor-isolated conformances
78+
let _: any Equatable.Type = EquatableStruct.self // expected-error{{main actor-isolated conformance of 'EquatableStruct' to 'Equatable' cannot be used in nonisolated context}}
79+
let _: any Hashable.Type = HashableStruct.self // expected-error{{main actor-isolated conformance of 'HashableStruct' to 'Hashable' cannot be used in nonisolated context}}
80+
let _: any RawRepresentable.Type = RawRepresentableEnum.self
81+
let _: any Encodable.Type = CodableClass.self // expected-error{{main actor-isolated conformance of 'CodableClass' to 'Encodable' cannot be used in nonisolated context}}
5882
}

0 commit comments

Comments
 (0)