Skip to content

Commit 9d493db

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 78234e2 commit 9d493db

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
@@ -713,6 +713,9 @@ class NormalProtocolConformance : public RootProtocolConformance,
713713
return ExplicitSafety::Unspecified;
714714
}
715715

716+
/// Whether this conformance has explicitly-specified global actor isolation.
717+
bool hasExplicitGlobalActorIsolation() const;
718+
716719
/// Determine whether we've lazily computed the associated conformance array
717720
/// already.
718721
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
@@ -1723,12 +1723,52 @@ bool swift::hasLetStoredPropertyWithInitialValue(NominalTypeDecl *nominal) {
17231723
});
17241724
}
17251725

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

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
@@ -7944,16 +7944,24 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance
79447944

79457945
auto dc = rootNormal->getDeclContext();
79467946
ASTContext &ctx = dc->getASTContext();
7947+
auto proto = rootNormal->getProtocol();
79477948

79487949
// If the protocol itself is isolated, don't infer isolation for the
79497950
// conformance.
7950-
if (getActorIsolation(rootNormal->getProtocol()).isActorIsolated())
7951+
if (getActorIsolation(proto).isActorIsolated())
7952+
return ActorIsolation::forNonisolated(false);
7953+
7954+
// SendableMetatype disables isolation inference.
7955+
auto sendableMetatypeProto =
7956+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
7957+
if (sendableMetatypeProto && proto->inheritsFrom(sendableMetatypeProto))
79517958
return ActorIsolation::forNonisolated(false);
79527959

79537960
// @preconcurrency disables isolation inference.
79547961
if (rootNormal->isPreconcurrency())
79557962
return ActorIsolation::forNonisolated(false);
79567963

7964+
79577965
// Isolation inference rules follow. If we aren't inferring isolated conformances,
79587966
// we're done.
79597967
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)